removal of python, uses golang testing
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"Engine/internal/model"
|
||||
"Engine/internal/service"
|
||||
@@ -23,6 +24,13 @@ func (h *BudgetHandler) Create(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusBadRequest, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
errs := req.Valid()
|
||||
if len(errs) > 0 {
|
||||
writeError(w, http.StatusBadRequest, strings.Join(errs, "; "))
|
||||
return
|
||||
}
|
||||
|
||||
budget, err := h.svc.Create(r.Context(), req)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
@@ -42,6 +50,13 @@ func (h *BudgetHandler) Update(w http.ResponseWriter, r *http.Request) {
|
||||
writeError(w, http.StatusBadRequest, "invalid request body")
|
||||
return
|
||||
}
|
||||
|
||||
errs := req.Valid()
|
||||
if len(errs) > 0 {
|
||||
writeError(w, http.StatusBadRequest, strings.Join(errs, "; "))
|
||||
return
|
||||
}
|
||||
|
||||
budget, err := h.svc.Update(r.Context(), id, req)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
|
||||
@@ -62,12 +62,79 @@ type CreateBudgetRequest struct {
|
||||
CreatedBy string `json:"created_by"`
|
||||
}
|
||||
|
||||
func (cBudget *CreateBudgetRequest) Valid() []string {
|
||||
var errs []string
|
||||
|
||||
if cBudget.FiscalYear < 1 || cBudget.FiscalYear > 2200 {
|
||||
errs = append(errs, "fiscal_year must be between 1 and 2200")
|
||||
}
|
||||
if cBudget.FiscalPeriod < 1 || cBudget.FiscalPeriod > 12 {
|
||||
errs = append(errs, "fiscal_period must be between 1 and 12")
|
||||
}
|
||||
if cBudget.Version == "" {
|
||||
errs = append(errs, "version is required")
|
||||
}
|
||||
if cBudget.DepartmentID == 0 {
|
||||
errs = append(errs, "department_id is required")
|
||||
}
|
||||
if cBudget.GLAccountID == 0 {
|
||||
errs = append(errs, "gl_account_id is required")
|
||||
}
|
||||
if cBudget.Amount <= 0 {
|
||||
errs = append(errs, "amount must be greater than 0")
|
||||
}
|
||||
if cBudget.Currency == "" {
|
||||
errs = append(errs, "currency is required")
|
||||
}
|
||||
if cBudget.CreatedBy == "" {
|
||||
errs = append(errs, "created_by is required")
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
type UpdateBudgetRequest struct {
|
||||
ID int `json:"id"`
|
||||
Amount float64 `json:"amount"`
|
||||
Notes string `json:"notes"`
|
||||
Version BudgetVersion `json:"version"`
|
||||
ChangedBy string `json:"created_by"`
|
||||
ID int `json:"id"`
|
||||
FiscalYear int `json:"fiscal_year"`
|
||||
FiscalPeriod int `json:"fiscal_period"`
|
||||
Version BudgetVersion `json:"version"`
|
||||
DepartmentID int `json:"department_id"`
|
||||
GLAccountID int `json:"gl_account_id"`
|
||||
Amount float64 `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
Notes string `json:"notes"`
|
||||
ChangedBy string `json:"created_by"`
|
||||
}
|
||||
|
||||
func (uBudget *UpdateBudgetRequest) Valid() []string {
|
||||
var errs []string
|
||||
|
||||
if uBudget.FiscalYear < 1 || uBudget.FiscalYear > 2200 {
|
||||
errs = append(errs, "fiscal_year must be between 1 and 2200")
|
||||
}
|
||||
if uBudget.FiscalPeriod < 1 || uBudget.FiscalPeriod > 12 {
|
||||
errs = append(errs, "fiscal_period must be between 1 and 12")
|
||||
}
|
||||
if uBudget.Version == "" {
|
||||
errs = append(errs, "version is required")
|
||||
}
|
||||
if uBudget.DepartmentID == 0 {
|
||||
errs = append(errs, "department_id is required")
|
||||
}
|
||||
if uBudget.GLAccountID == 0 {
|
||||
errs = append(errs, "gl_account_id is required")
|
||||
}
|
||||
if uBudget.Amount <= 0 {
|
||||
errs = append(errs, "amount must be greater than 0")
|
||||
}
|
||||
if uBudget.Currency == "" {
|
||||
errs = append(errs, "currency is required")
|
||||
}
|
||||
if uBudget.ChangedBy == "" {
|
||||
errs = append(errs, "created_by is required")
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
type Actual struct {
|
||||
@@ -93,6 +160,34 @@ type IngestActualsRequest struct {
|
||||
Source string `json:"source"`
|
||||
}
|
||||
|
||||
func (i *IngestActualsRequest) Valid() []string {
|
||||
var errs []string
|
||||
|
||||
if i.FiscalYear < 1 || i.FiscalYear > 2200 {
|
||||
errs = append(errs, "fiscal_year must be between 1 and 2200")
|
||||
}
|
||||
if i.FiscalPeriod < 1 || i.FiscalPeriod > 12 {
|
||||
errs = append(errs, "fiscal_period must be between 1 and 12")
|
||||
}
|
||||
if i.DeptCode == "" {
|
||||
errs = append(errs, "dept_code is required")
|
||||
}
|
||||
if i.GLCode == "" {
|
||||
errs = append(errs, "gl_code is required")
|
||||
}
|
||||
if i.Amount <= 0 {
|
||||
errs = append(errs, "amount must be greater than 0")
|
||||
}
|
||||
if i.Currency == "" {
|
||||
errs = append(errs, "currency is required")
|
||||
}
|
||||
if i.Source == "" {
|
||||
errs = append(errs, "source is required")
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
type VarianceStatus string
|
||||
|
||||
const (
|
||||
|
||||
334
internal/test/refrence_test.go
Normal file
334
internal/test/refrence_test.go
Normal file
@@ -0,0 +1,334 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"Engine/internal/database"
|
||||
"Engine/internal/handler"
|
||||
"Engine/internal/model"
|
||||
"Engine/tests/internal/testutil"
|
||||
)
|
||||
|
||||
// ── wire helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
func newReferenceHandler(t *testing.T) *handler.ReferenceHandler {
|
||||
t.Helper()
|
||||
return handler.NewReferenceHandler(database.NewReferenceRepo(testutil.NewTestDB(t)))
|
||||
}
|
||||
|
||||
// newReferenceServer spins up a real httptest.Server with the production
|
||||
// mux routes. Use this for delete/path-param tests that need {id} routing.
|
||||
func newReferenceServer(t *testing.T) *httptest.Server {
|
||||
t.Helper()
|
||||
h := newReferenceHandler(t)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("POST /api/v1/departments", h.CreateDepartment)
|
||||
mux.HandleFunc("GET /api/v1/departments", h.ListDepartments)
|
||||
mux.HandleFunc("DELETE /api/v1/departments/{id}", h.DeleteDepartment)
|
||||
mux.HandleFunc("POST /api/v1/gl-accounts", h.CreateGLAccount)
|
||||
mux.HandleFunc("GET /api/v1/gl-accounts", h.ListGLAccounts)
|
||||
mux.HandleFunc("DELETE /api/v1/gl-accounts/{id}", h.DeleteGLAccount)
|
||||
|
||||
srv := httptest.NewServer(mux)
|
||||
t.Cleanup(srv.Close)
|
||||
return srv
|
||||
}
|
||||
|
||||
// ── Department: Create ────────────────────────────────────────────────────────
|
||||
|
||||
func TestCreateDepartment_OK(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
|
||||
w := testutil.Do(t, http.HandlerFunc(h.CreateDepartment), http.MethodPost, "/",
|
||||
map[string]any{"code": "ENG", "name": "Engineering", "active": true})
|
||||
|
||||
testutil.AssertStatus(t, w, http.StatusCreated)
|
||||
|
||||
var got database.Department
|
||||
testutil.DecodeJSON(t, w, &got)
|
||||
if got.Code != "ENG" {
|
||||
t.Errorf("code: got %q, want %q", got.Code, "ENG")
|
||||
}
|
||||
if got.ID == 0 {
|
||||
t.Error("expected non-zero ID in response")
|
||||
}
|
||||
if !got.Active {
|
||||
t.Error("expected active=true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateDepartment_DefaultsActiveTrue(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
|
||||
// active field omitted — handler must default it to true
|
||||
w := testutil.Do(t, http.HandlerFunc(h.CreateDepartment), http.MethodPost, "/",
|
||||
map[string]any{"code": "MKT", "name": "Marketing"})
|
||||
|
||||
testutil.AssertStatus(t, w, http.StatusCreated)
|
||||
|
||||
var got database.Department
|
||||
testutil.DecodeJSON(t, w, &got)
|
||||
if !got.Active {
|
||||
t.Error("expected active to default to true when omitted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateDepartment_MissingCode(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
w := testutil.Do(t, http.HandlerFunc(h.CreateDepartment), http.MethodPost, "/",
|
||||
map[string]any{"name": "Engineering"})
|
||||
testutil.AssertStatus(t, w, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func TestCreateDepartment_MissingName(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
w := testutil.Do(t, http.HandlerFunc(h.CreateDepartment), http.MethodPost, "/",
|
||||
map[string]any{"code": "ENG"})
|
||||
testutil.AssertStatus(t, w, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func TestCreateDepartment_WhitespaceOnly(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
w := testutil.Do(t, http.HandlerFunc(h.CreateDepartment), http.MethodPost, "/",
|
||||
map[string]any{"code": " ", "name": " "})
|
||||
testutil.AssertStatus(t, w, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func TestCreateDepartment_InvalidJSON(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
// nil body → empty body → JSON decode fails → 400
|
||||
w := testutil.Do(t, http.HandlerFunc(h.CreateDepartment), http.MethodPost, "/", nil)
|
||||
testutil.AssertStatus(t, w, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func TestCreateDepartment_Upsert(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
fn := http.HandlerFunc(h.CreateDepartment)
|
||||
|
||||
testutil.Do(t, fn, http.MethodPost, "/", map[string]any{"code": "FIN", "name": "Finance"})
|
||||
|
||||
w := testutil.Do(t, fn, http.MethodPost, "/", map[string]any{"code": "FIN", "name": "Finance Updated"})
|
||||
testutil.AssertStatus(t, w, http.StatusCreated)
|
||||
|
||||
var got database.Department
|
||||
testutil.DecodeJSON(t, w, &got)
|
||||
if got.Name != "Finance Updated" {
|
||||
t.Errorf("upsert name: got %q, want %q", got.Name, "Finance Updated")
|
||||
}
|
||||
}
|
||||
|
||||
// ── Department: List ──────────────────────────────────────────────────────────
|
||||
|
||||
func TestListDepartments_Empty(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
|
||||
w := testutil.Do(t, http.HandlerFunc(h.ListDepartments), http.MethodGet, "/", nil)
|
||||
testutil.AssertStatus(t, w, http.StatusOK)
|
||||
|
||||
var got []database.Department
|
||||
testutil.DecodeJSON(t, w, &got)
|
||||
if len(got) != 0 {
|
||||
t.Errorf("expected empty list, got %d", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestListDepartments_ReturnAll(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
fn := http.HandlerFunc(h.CreateDepartment)
|
||||
|
||||
testutil.Do(t, fn, http.MethodPost, "/", map[string]any{"code": "HR", "name": "Human Resources"})
|
||||
testutil.Do(t, fn, http.MethodPost, "/", map[string]any{"code": "IT", "name": "Information Technology"})
|
||||
testutil.Do(t, fn, http.MethodPost, "/", map[string]any{"code": "OPS", "name": "Operations"})
|
||||
|
||||
w := testutil.Do(t, http.HandlerFunc(h.ListDepartments), http.MethodGet, "/", nil)
|
||||
testutil.AssertStatus(t, w, http.StatusOK)
|
||||
|
||||
var got []database.Department
|
||||
testutil.DecodeJSON(t, w, &got)
|
||||
if len(got) != 3 {
|
||||
t.Errorf("expected 3 departments, got %d", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
// ── Department: Delete ────────────────────────────────────────────────────────
|
||||
|
||||
func TestDeleteDepartment_OK(t *testing.T) {
|
||||
srv := newReferenceServer(t)
|
||||
client := srv.Client()
|
||||
|
||||
resp, err := client.Post(srv.URL+"/api/v1/departments", "application/json",
|
||||
mustJSON(t, map[string]any{"code": "OPS", "name": "Operations"}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var created database.Department
|
||||
json.NewDecoder(resp.Body).Decode(&created)
|
||||
resp.Body.Close()
|
||||
|
||||
req, _ := http.NewRequest(http.MethodDelete, srv.URL+"/api/v1/departments/"+strconv.Itoa(created.ID), nil)
|
||||
resp2, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp2.Body.Close()
|
||||
|
||||
if resp2.StatusCode != http.StatusNoContent && resp2.StatusCode != http.StatusOK {
|
||||
t.Errorf("delete: got %d, want 200 or 204", resp2.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteDepartment_NotFound(t *testing.T) {
|
||||
srv := newReferenceServer(t)
|
||||
|
||||
req, _ := http.NewRequest(http.MethodDelete, srv.URL+"/api/v1/departments/9999", nil)
|
||||
resp, err := srv.Client().Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
// Accept 404 or 204 — adjust to match your handler's behaviour
|
||||
if resp.StatusCode != http.StatusNotFound && resp.StatusCode != http.StatusNoContent {
|
||||
t.Errorf("delete non-existent: got %d, want 404 or 204", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// ── GL Account: Create ────────────────────────────────────────────────────────
|
||||
|
||||
func TestCreateGLAccount_OK(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
|
||||
w := testutil.Do(t, http.HandlerFunc(h.CreateGLAccount), http.MethodPost, "/",
|
||||
map[string]any{"code": "5001", "name": "Travel Expenses", "type": "expense"})
|
||||
|
||||
testutil.AssertStatus(t, w, http.StatusCreated)
|
||||
|
||||
var got database.GLAccount
|
||||
testutil.DecodeJSON(t, w, &got)
|
||||
if got.Code != "5001" {
|
||||
t.Errorf("code: got %q, want %q", got.Code, "5001")
|
||||
}
|
||||
if got.ID == 0 {
|
||||
t.Error("expected non-zero ID")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateGLAccount_MissingCode(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
w := testutil.Do(t, http.HandlerFunc(h.CreateGLAccount), http.MethodPost, "/",
|
||||
map[string]any{"name": "Travel Expenses"})
|
||||
testutil.AssertStatus(t, w, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func TestCreateGLAccount_MissingName(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
w := testutil.Do(t, http.HandlerFunc(h.CreateGLAccount), http.MethodPost, "/",
|
||||
map[string]any{"code": "5001"})
|
||||
testutil.AssertStatus(t, w, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func TestCreateGLAccount_Upsert(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
fn := http.HandlerFunc(h.CreateGLAccount)
|
||||
|
||||
testutil.Do(t, fn, http.MethodPost, "/", map[string]any{"code": "4001", "name": "Revenue"})
|
||||
|
||||
w := testutil.Do(t, fn, http.MethodPost, "/", map[string]any{"code": "4001", "name": "Revenue Updated"})
|
||||
testutil.AssertStatus(t, w, http.StatusCreated)
|
||||
|
||||
var got model.GLAccount
|
||||
testutil.DecodeJSON(t, w, &got)
|
||||
if got.Name != "Revenue Updated" {
|
||||
t.Errorf("upsert name: got %q, want %q", got.Name, "Revenue Updated")
|
||||
}
|
||||
}
|
||||
|
||||
// ── GL Account: List ──────────────────────────────────────────────────────────
|
||||
|
||||
func TestListGLAccounts_Empty(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
w := testutil.Do(t, http.HandlerFunc(h.ListGLAccounts), http.MethodGet, "/", nil)
|
||||
testutil.AssertStatus(t, w, http.StatusOK)
|
||||
|
||||
var got []database.GLAccount
|
||||
testutil.DecodeJSON(t, w, &got)
|
||||
if len(got) != 0 {
|
||||
t.Errorf("expected empty list, got %d", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestListGLAccounts_ReturnAll(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
fn := http.HandlerFunc(h.CreateGLAccount)
|
||||
|
||||
testutil.Do(t, fn, http.MethodPost, "/", map[string]any{"code": "4001", "name": "Revenue"})
|
||||
testutil.Do(t, fn, http.MethodPost, "/", map[string]any{"code": "5001", "name": "COGS"})
|
||||
|
||||
w := testutil.Do(t, http.HandlerFunc(h.ListGLAccounts), http.MethodGet, "/", nil)
|
||||
testutil.AssertStatus(t, w, http.StatusOK)
|
||||
|
||||
var got []database.GLAccount
|
||||
testutil.DecodeJSON(t, w, &got)
|
||||
if len(got) != 2 {
|
||||
t.Errorf("expected 2 GL accounts, got %d", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
// ── GL Account: Delete ────────────────────────────────────────────────────────
|
||||
|
||||
func TestDeleteGLAccount_OK(t *testing.T) {
|
||||
srv := newReferenceServer(t)
|
||||
client := srv.Client()
|
||||
|
||||
resp, err := client.Post(srv.URL+"/api/v1/gl-accounts", "application/json",
|
||||
mustJSON(t, map[string]any{"code": "6001", "name": "Rent"}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var created database.GLAccount
|
||||
json.NewDecoder(resp.Body).Decode(&created)
|
||||
resp.Body.Close()
|
||||
|
||||
req, _ := http.NewRequest(http.MethodDelete, srv.URL+"/api/v1/gl-accounts/"+strconv.Itoa(created.ID), nil)
|
||||
resp2, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp2.Body.Close()
|
||||
|
||||
if resp2.StatusCode != http.StatusNoContent && resp2.StatusCode != http.StatusOK {
|
||||
t.Errorf("delete: got %d, want 200 or 204", resp2.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteGLAccount_NotFound(t *testing.T) {
|
||||
srv := newReferenceServer(t)
|
||||
|
||||
req, _ := http.NewRequest(http.MethodDelete, srv.URL+"/api/v1/gl-accounts/9999", nil)
|
||||
resp, err := srv.Client().Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusNotFound && resp.StatusCode != http.StatusNoContent {
|
||||
t.Errorf("delete non-existent: got %d, want 404 or 204", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// ── local helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
func mustJSON(t *testing.T, v any) *bytes.Reader {
|
||||
t.Helper()
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return bytes.NewReader(b)
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
# FP&A Test Data Platform
|
||||
|
||||
Python tooling to generate, validate, and load realistic FP&A data into your Go API.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
testing/
|
||||
├── generators/
|
||||
│ └── generate_data.py # Creates all CSV files
|
||||
├── loaders/
|
||||
│ └── api_loader.py # POSTs CSVs to your Go API
|
||||
├── tests/
|
||||
│ └── test_fpa.py # Data integrity + API tests
|
||||
├── data/
|
||||
│ └── csv/ # Generated CSV files land here
|
||||
└── requirements.txt
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
|
||||
# 1. Generate all CSV data
|
||||
python generators/generate_data.py
|
||||
|
||||
# 2. Validate data integrity (no API needed)
|
||||
pytest tests/test_fpa.py -v
|
||||
|
||||
# 3. Load into your Go API (dry-run first)
|
||||
python loaders/api_loader.py --dry-run
|
||||
|
||||
# 4. Load for real
|
||||
python loaders/api_loader.py --url http://localhost:8080
|
||||
```
|
||||
|
||||
## Generated Datasets
|
||||
|
||||
| File | Rows | Description |
|
||||
|------|------|-------------|
|
||||
| `revenue_budget_vs_actuals.csv` | 48 | Product & Service revenue — budget vs actuals, 24 months |
|
||||
| `opex_budget_vs_actuals.csv` | ~2,688 | Dept × category opex — budget vs actuals |
|
||||
| `pl_income_statement.csv` | 24 | Monthly P&L: revenue, COGS, gross profit, EBITDA, net income |
|
||||
| `cash_flow.csv` | 24 | Operating / investing / financing cash flows, rolling balance |
|
||||
| `headcount_workforce.csv` | ~1,000+ | Employee snapshots per month, with hire/term dates & salaries |
|
||||
|
||||
## Key Concepts
|
||||
|
||||
**Budget vs Actuals** — Every financial row has a `budget_amount` (what was planned) and
|
||||
`actual_amount` (what really happened). The `variance` = actual − budget. Positive variance
|
||||
on revenue = good. Positive variance on spend = over budget.
|
||||
|
||||
**Product vs Service revenue** — Product is recurring SaaS subscriptions (~70%, higher margin).
|
||||
Service is consulting/support (~30%, lower margin). Both grow monthly at different rates.
|
||||
|
||||
**Cash Flow** — Separate from revenue. Collections can lag invoicing (DSO effect). Includes
|
||||
a simulated Series A raise in June 2023.
|
||||
|
||||
## API Endpoints (from main.go)
|
||||
|
||||
```
|
||||
POST /api/v1/budgets ← create one budget line
|
||||
PUT /api/v1/budgets/{id} ← update a budget line
|
||||
DELETE /api/v1/budgets/{id} ← delete a budget line
|
||||
POST /api/v1/actuals/ingest ← bulk ingest actuals { "records": [...] }
|
||||
GET /api/v1/variance ← variance report (budget vs actuals)
|
||||
GET /api/v1/variance/alerts ← over/under budget alerts
|
||||
GET /api/v1/health ← db health check
|
||||
```
|
||||
|
||||
## Load Order
|
||||
|
||||
The seeder always loads in this order — **budgets must exist before actuals**:
|
||||
|
||||
1. **Budgets** — `POST /api/v1/budgets` individually (revenue + opex lines)
|
||||
2. **Actuals** — `POST /api/v1/actuals/ingest` in batches of 50
|
||||
3. **Variance check** — `GET /api/v1/variance` to confirm data landed
|
||||
|
||||
## Loader Options
|
||||
|
||||
```bash
|
||||
python loaders/api_loader.py --help
|
||||
|
||||
--url API base URL (default: http://localhost:8080)
|
||||
--batch Records per actuals/ingest request (default: 50)
|
||||
--dry-run Print payloads without sending
|
||||
--token Bearer token for auth header
|
||||
--only Run one step only: budgets | actuals | variance | alerts
|
||||
```
|
||||
@@ -1,25 +0,0 @@
|
||||
company,year,month,period,cash_collected_product,cash_collected_service,cash_paid_opex,cash_paid_cogs,net_operating_cash_flow,capex,net_investing_cash_flow,loan_repayment,equity_raised,net_financing_cash_flow,net_change_in_cash,closing_cash_balance
|
||||
AcmeSaaS Inc.,2023,1,2023-01,163879.88,63878.3,261115.75,83104.06,-116461.63,6403.87,-6403.87,0.0,0.0,0.0,-122865.5,1077134.5
|
||||
AcmeSaaS Inc.,2023,2,2023-02,178115.66,69069.82,255846.74,76652.29,-85313.55,5786.84,-5786.84,0.0,0.0,0.0,-91100.39,986034.11
|
||||
AcmeSaaS Inc.,2023,3,2023-03,183078.97,71995.04,275445.39,76630.76,-97002.14,0.0,-0.0,5282.91,0.0,-5282.91,-102285.05,883749.06
|
||||
AcmeSaaS Inc.,2023,4,2023-04,185844.77,72290.05,275275.82,85242.75,-102383.75,0.0,-0.0,0.0,0.0,0.0,-102383.75,781365.31
|
||||
AcmeSaaS Inc.,2023,5,2023-05,199095.24,71446.54,276860.19,80225.21,-86543.62,7899.21,-7899.21,0.0,0.0,0.0,-94442.83,686922.48
|
||||
AcmeSaaS Inc.,2023,6,2023-06,186599.26,73957.63,282518.88,93079.84,-115041.83,0.0,-0.0,4741.08,500000.0,495258.92,380217.09,1067139.57
|
||||
AcmeSaaS Inc.,2023,7,2023-07,199774.68,71671.33,273476.83,86473.31,-88504.13,0.0,-0.0,0.0,0.0,0.0,-88504.13,978635.44
|
||||
AcmeSaaS Inc.,2023,8,2023-08,206079.53,71897.22,286478.13,88563.47,-97064.85,7988.9,-7988.9,0.0,0.0,0.0,-105053.75,873581.69
|
||||
AcmeSaaS Inc.,2023,9,2023-09,208715.04,76023.16,284930.26,98913.54,-99105.6,0.0,-0.0,4705.79,0.0,-4705.79,-103811.39,769770.3
|
||||
AcmeSaaS Inc.,2023,10,2023-10,202037.42,77115.77,302273.74,99395.35,-122515.9,10447.72,-10447.72,0.0,0.0,0.0,-132963.62,636806.68
|
||||
AcmeSaaS Inc.,2023,11,2023-11,224870.49,81643.64,292828.92,97493.13,-83807.92,0.0,-0.0,0.0,0.0,0.0,-83807.92,552998.76
|
||||
AcmeSaaS Inc.,2023,12,2023-12,224481.39,82875.26,304003.52,105371.0,-102017.87,0.0,-0.0,5589.79,0.0,-5589.79,-107607.66,445391.1
|
||||
AcmeSaaS Inc.,2024,1,2024-01,225535.21,86372.23,306496.22,94450.84,-89039.62,0.0,-0.0,0.0,0.0,0.0,-89039.62,356351.48
|
||||
AcmeSaaS Inc.,2024,2,2024-02,231002.6,81570.67,309999.41,104620.68,-102046.82,0.0,-0.0,0.0,0.0,0.0,-102046.82,254304.66
|
||||
AcmeSaaS Inc.,2024,3,2024-03,255843.55,86327.17,312424.94,108228.6,-78482.82,0.0,-0.0,5631.5,0.0,-5631.5,-84114.32,170190.34
|
||||
AcmeSaaS Inc.,2024,4,2024-04,243746.7,86737.49,333771.4,110195.35,-113482.56,0.0,-0.0,0.0,0.0,0.0,-113482.56,56707.78
|
||||
AcmeSaaS Inc.,2024,5,2024-05,253076.43,92057.75,335130.33,110912.96,-100909.11,0.0,-0.0,0.0,0.0,0.0,-100909.11,-44201.33
|
||||
AcmeSaaS Inc.,2024,6,2024-06,264225.36,88606.97,327425.58,118967.82,-93561.07,0.0,-0.0,4037.87,0.0,-4037.87,-97598.94,-141800.27
|
||||
AcmeSaaS Inc.,2024,7,2024-07,260739.02,83323.95,340807.79,120273.6,-117018.42,5198.6,-5198.6,0.0,0.0,0.0,-122217.02,-264017.29
|
||||
AcmeSaaS Inc.,2024,8,2024-08,261496.73,85375.07,335055.79,111383.21,-99567.2,6990.89,-6990.89,0.0,0.0,0.0,-106558.09,-370575.38
|
||||
AcmeSaaS Inc.,2024,9,2024-09,275303.9,94869.09,351269.66,114058.86,-95155.53,0.0,-0.0,5116.17,0.0,-5116.17,-100271.7,-470847.08
|
||||
AcmeSaaS Inc.,2024,10,2024-10,277726.03,99258.56,336970.32,118601.63,-78587.36,0.0,-0.0,0.0,0.0,0.0,-78587.36,-549434.44
|
||||
AcmeSaaS Inc.,2024,11,2024-11,309709.89,98087.4,341625.12,132656.81,-66484.64,10909.5,-10909.5,0.0,0.0,0.0,-77394.14,-626828.58
|
||||
AcmeSaaS Inc.,2024,12,2024-12,312778.51,91878.14,353027.96,122611.82,-70983.13,6093.24,-6093.24,5077.16,0.0,-5077.16,-82153.53,-708982.11
|
||||
|
@@ -1,422 +0,0 @@
|
||||
company,employee_id,department,role,hire_date,termination_date,status,annual_salary_budget,actual_salary_paid_ytd,year,month,period,headcount_fte
|
||||
AcmeSaaS Inc.,EMP1000,Engineering,Engineering Manager,2022-03-04,,Active,180000,14850.0,2023,1,2023-01,1.0
|
||||
AcmeSaaS Inc.,EMP1001,Engineering,Senior Engineer,2022-04-02,,Active,160000,13466.67,2023,1,2023-01,1.0
|
||||
AcmeSaaS Inc.,EMP1002,Engineering,Senior Engineer,2022-11-08,,Active,160000,13466.67,2023,1,2023-01,1.0
|
||||
AcmeSaaS Inc.,EMP1003,Engineering,Software Engineer,2022-02-14,,Active,120000,9900.0,2023,1,2023-01,1.0
|
||||
AcmeSaaS Inc.,EMP1004,Sales,SDR,2022-08-04,,Active,65000,5308.33,2023,1,2023-01,1.0
|
||||
AcmeSaaS Inc.,EMP1005,Sales,SDR,2022-03-01,,Active,65000,5362.5,2023,1,2023-01,1.0
|
||||
AcmeSaaS Inc.,EMP1006,Sales,SDR,2022-03-14,,Active,65000,5416.67,2023,1,2023-01,1.0
|
||||
AcmeSaaS Inc.,EMP1007,Marketing,Content Strategist,2022-08-21,,Active,80000,6800.0,2023,1,2023-01,1.0
|
||||
AcmeSaaS Inc.,EMP1008,Marketing,Marketing Manager,2022-05-11,,Active,110000,9075.0,2023,1,2023-01,1.0
|
||||
AcmeSaaS Inc.,EMP1009,Marketing,Content Strategist,2022-11-02,,Active,80000,6666.67,2023,1,2023-01,1.0
|
||||
AcmeSaaS Inc.,EMP1010,Marketing,Marketing Manager,2022-11-19,,Active,110000,9258.33,2023,1,2023-01,1.0
|
||||
AcmeSaaS Inc.,EMP1011,Operations,Finance Analyst,2022-12-24,,Active,95000,7916.67,2023,1,2023-01,1.0
|
||||
AcmeSaaS Inc.,EMP1012,Operations,Operations Manager,2022-03-14,,Active,115000,9775.0,2023,1,2023-01,1.0
|
||||
AcmeSaaS Inc.,EMP1013,Sales,Account Executive,2023-01-01,,Active,90000,7425.0,2023,1,2023-01,0.5
|
||||
AcmeSaaS Inc.,EMP1000,Engineering,Engineering Manager,2022-03-04,,Active,180000,30300.0,2023,2,2023-02,1.0
|
||||
AcmeSaaS Inc.,EMP1001,Engineering,Senior Engineer,2022-04-02,,Active,160000,26400.0,2023,2,2023-02,1.0
|
||||
AcmeSaaS Inc.,EMP1002,Engineering,Senior Engineer,2022-11-08,,Active,160000,26933.33,2023,2,2023-02,1.0
|
||||
AcmeSaaS Inc.,EMP1003,Engineering,Software Engineer,2022-02-14,,Active,120000,20200.0,2023,2,2023-02,1.0
|
||||
AcmeSaaS Inc.,EMP1004,Sales,SDR,2022-08-04,,Active,65000,10941.67,2023,2,2023-02,1.0
|
||||
AcmeSaaS Inc.,EMP1005,Sales,SDR,2022-03-01,,Active,65000,10616.67,2023,2,2023-02,1.0
|
||||
AcmeSaaS Inc.,EMP1006,Sales,SDR,2022-03-14,,Active,65000,10725.0,2023,2,2023-02,1.0
|
||||
AcmeSaaS Inc.,EMP1007,Marketing,Content Strategist,2022-08-21,,Active,80000,13200.0,2023,2,2023-02,1.0
|
||||
AcmeSaaS Inc.,EMP1008,Marketing,Marketing Manager,2022-05-11,,Active,110000,18516.67,2023,2,2023-02,1.0
|
||||
AcmeSaaS Inc.,EMP1009,Marketing,Content Strategist,2022-11-02,,Active,80000,13200.0,2023,2,2023-02,1.0
|
||||
AcmeSaaS Inc.,EMP1010,Marketing,Marketing Manager,2022-11-19,,Active,110000,18333.33,2023,2,2023-02,1.0
|
||||
AcmeSaaS Inc.,EMP1011,Operations,Finance Analyst,2022-12-24,,Active,95000,15991.67,2023,2,2023-02,1.0
|
||||
AcmeSaaS Inc.,EMP1012,Operations,Operations Manager,2022-03-14,,Active,115000,19166.67,2023,2,2023-02,1.0
|
||||
AcmeSaaS Inc.,EMP1013,Sales,Account Executive,2023-01-01,,Active,90000,15000.0,2023,2,2023-02,0.5
|
||||
AcmeSaaS Inc.,EMP1000,Engineering,Engineering Manager,2022-03-04,,Active,180000,44100.0,2023,3,2023-03,1.0
|
||||
AcmeSaaS Inc.,EMP1001,Engineering,Senior Engineer,2022-04-02,,Active,160000,40800.0,2023,3,2023-03,1.0
|
||||
AcmeSaaS Inc.,EMP1002,Engineering,Senior Engineer,2022-11-08,,Active,160000,40000.0,2023,3,2023-03,1.0
|
||||
AcmeSaaS Inc.,EMP1003,Engineering,Software Engineer,2022-02-14,,Active,120000,30600.0,2023,3,2023-03,1.0
|
||||
AcmeSaaS Inc.,EMP1004,Sales,SDR,2022-08-04,,Active,65000,15925.0,2023,3,2023-03,1.0
|
||||
AcmeSaaS Inc.,EMP1005,Sales,SDR,2022-03-01,,Active,65000,16250.0,2023,3,2023-03,1.0
|
||||
AcmeSaaS Inc.,EMP1006,Sales,SDR,2022-03-14,,Active,65000,16250.0,2023,3,2023-03,1.0
|
||||
AcmeSaaS Inc.,EMP1007,Marketing,Content Strategist,2022-08-21,,Active,80000,19800.0,2023,3,2023-03,1.0
|
||||
AcmeSaaS Inc.,EMP1008,Marketing,Marketing Manager,2022-05-11,,Active,110000,27225.0,2023,3,2023-03,1.0
|
||||
AcmeSaaS Inc.,EMP1009,Marketing,Content Strategist,2022-11-02,,Active,80000,20000.0,2023,3,2023-03,1.0
|
||||
AcmeSaaS Inc.,EMP1010,Marketing,Marketing Manager,2022-11-19,,Active,110000,27500.0,2023,3,2023-03,1.0
|
||||
AcmeSaaS Inc.,EMP1011,Operations,Finance Analyst,2022-12-24,,Active,95000,23512.5,2023,3,2023-03,1.0
|
||||
AcmeSaaS Inc.,EMP1012,Operations,Operations Manager,2022-03-14,,Active,115000,28462.5,2023,3,2023-03,1.0
|
||||
AcmeSaaS Inc.,EMP1013,Sales,Account Executive,2023-01-01,,Active,90000,22275.0,2023,3,2023-03,0.5
|
||||
AcmeSaaS Inc.,EMP1000,Engineering,Engineering Manager,2022-03-04,,Active,180000,60000.0,2023,4,2023-04,1.0
|
||||
AcmeSaaS Inc.,EMP1001,Engineering,Senior Engineer,2022-04-02,,Active,160000,54400.0,2023,4,2023-04,1.0
|
||||
AcmeSaaS Inc.,EMP1002,Engineering,Senior Engineer,2022-11-08,,Active,160000,53866.67,2023,4,2023-04,1.0
|
||||
AcmeSaaS Inc.,EMP1003,Engineering,Software Engineer,2022-02-14,,Active,120000,39600.0,2023,4,2023-04,1.0
|
||||
AcmeSaaS Inc.,EMP1004,Sales,SDR,2022-08-04,,Active,65000,21233.33,2023,4,2023-04,1.0
|
||||
AcmeSaaS Inc.,EMP1005,Sales,SDR,2022-03-01,,Active,65000,22100.0,2023,4,2023-04,1.0
|
||||
AcmeSaaS Inc.,EMP1006,Sales,SDR,2022-03-14,,Active,65000,21450.0,2023,4,2023-04,1.0
|
||||
AcmeSaaS Inc.,EMP1007,Marketing,Content Strategist,2022-08-21,,Active,80000,26666.67,2023,4,2023-04,1.0
|
||||
AcmeSaaS Inc.,EMP1008,Marketing,Marketing Manager,2022-05-11,,Active,110000,36300.0,2023,4,2023-04,1.0
|
||||
AcmeSaaS Inc.,EMP1009,Marketing,Content Strategist,2022-11-02,,Active,80000,26400.0,2023,4,2023-04,1.0
|
||||
AcmeSaaS Inc.,EMP1010,Marketing,Marketing Manager,2022-11-19,,Active,110000,36300.0,2023,4,2023-04,1.0
|
||||
AcmeSaaS Inc.,EMP1011,Operations,Finance Analyst,2022-12-24,,Active,95000,31983.33,2023,4,2023-04,1.0
|
||||
AcmeSaaS Inc.,EMP1012,Operations,Operations Manager,2022-03-14,,Active,115000,38333.33,2023,4,2023-04,1.0
|
||||
AcmeSaaS Inc.,EMP1013,Sales,Account Executive,2023-01-01,,Active,90000,30000.0,2023,4,2023-04,0.5
|
||||
AcmeSaaS Inc.,EMP1014,Operations,Customer Success,2023-04-01,,Active,75000,6250.0,2023,4,2023-04,1.0
|
||||
AcmeSaaS Inc.,EMP1000,Engineering,Engineering Manager,2022-03-04,,Active,180000,75750.0,2023,5,2023-05,1.0
|
||||
AcmeSaaS Inc.,EMP1001,Engineering,Senior Engineer,2022-04-02,,Active,160000,66000.0,2023,5,2023-05,1.0
|
||||
AcmeSaaS Inc.,EMP1002,Engineering,Senior Engineer,2022-11-08,,Active,160000,66000.0,2023,5,2023-05,1.0
|
||||
AcmeSaaS Inc.,EMP1003,Engineering,Software Engineer,2022-02-14,,Active,120000,50500.0,2023,5,2023-05,1.0
|
||||
AcmeSaaS Inc.,EMP1004,Sales,SDR,2022-08-04,,Active,65000,27354.17,2023,5,2023-05,1.0
|
||||
AcmeSaaS Inc.,EMP1005,Sales,SDR,2022-03-01,,Active,65000,26541.67,2023,5,2023-05,1.0
|
||||
AcmeSaaS Inc.,EMP1006,Sales,SDR,2022-03-14,,Active,65000,27083.33,2023,5,2023-05,1.0
|
||||
AcmeSaaS Inc.,EMP1007,Marketing,Content Strategist,2022-08-21,,Active,80000,33666.67,2023,5,2023-05,1.0
|
||||
AcmeSaaS Inc.,EMP1008,Marketing,Marketing Manager,2022-05-11,,Active,110000,44916.67,2023,5,2023-05,1.0
|
||||
AcmeSaaS Inc.,EMP1009,Marketing,Content Strategist,2022-11-02,,Active,80000,33333.33,2023,5,2023-05,1.0
|
||||
AcmeSaaS Inc.,EMP1010,Marketing,Marketing Manager,2022-11-19,,Active,110000,44916.67,2023,5,2023-05,1.0
|
||||
AcmeSaaS Inc.,EMP1011,Operations,Finance Analyst,2022-12-24,,Active,95000,39583.33,2023,5,2023-05,1.0
|
||||
AcmeSaaS Inc.,EMP1012,Operations,Operations Manager,2022-03-14,,Active,115000,47916.67,2023,5,2023-05,1.0
|
||||
AcmeSaaS Inc.,EMP1013,Sales,Account Executive,2023-01-01,,Active,90000,37500.0,2023,5,2023-05,0.5
|
||||
AcmeSaaS Inc.,EMP1014,Operations,Customer Success,2023-04-01,,Active,75000,12375.0,2023,5,2023-05,1.0
|
||||
AcmeSaaS Inc.,EMP1000,Engineering,Engineering Manager,2022-03-04,,Active,180000,88200.0,2023,6,2023-06,1.0
|
||||
AcmeSaaS Inc.,EMP1001,Engineering,Senior Engineer,2022-04-02,,Active,160000,79200.0,2023,6,2023-06,1.0
|
||||
AcmeSaaS Inc.,EMP1002,Engineering,Senior Engineer,2022-11-08,,Active,160000,80000.0,2023,6,2023-06,1.0
|
||||
AcmeSaaS Inc.,EMP1003,Engineering,Software Engineer,2022-02-14,,Active,120000,58800.0,2023,6,2023-06,1.0
|
||||
AcmeSaaS Inc.,EMP1004,Sales,SDR,2022-08-04,,Active,65000,32500.0,2023,6,2023-06,1.0
|
||||
AcmeSaaS Inc.,EMP1005,Sales,SDR,2022-03-01,,Active,65000,32825.0,2023,6,2023-06,1.0
|
||||
AcmeSaaS Inc.,EMP1006,Sales,SDR,2022-03-14,,Active,65000,32500.0,2023,6,2023-06,1.0
|
||||
AcmeSaaS Inc.,EMP1007,Marketing,Content Strategist,2022-08-21,,Active,80000,40000.0,2023,6,2023-06,1.0
|
||||
AcmeSaaS Inc.,EMP1008,Marketing,Marketing Manager,2022-05-11,,Active,110000,55000.0,2023,6,2023-06,1.0
|
||||
AcmeSaaS Inc.,EMP1009,Marketing,Content Strategist,2022-11-02,,Active,80000,39200.0,2023,6,2023-06,1.0
|
||||
AcmeSaaS Inc.,EMP1010,Marketing,Marketing Manager,2022-11-19,,Active,110000,55000.0,2023,6,2023-06,1.0
|
||||
AcmeSaaS Inc.,EMP1011,Operations,Finance Analyst,2022-12-24,,Active,95000,47975.0,2023,6,2023-06,1.0
|
||||
AcmeSaaS Inc.,EMP1012,Operations,Operations Manager,2022-03-14,,Active,115000,58075.0,2023,6,2023-06,1.0
|
||||
AcmeSaaS Inc.,EMP1013,Sales,Account Executive,2023-01-01,,Active,90000,45000.0,2023,6,2023-06,0.5
|
||||
AcmeSaaS Inc.,EMP1014,Operations,Customer Success,2023-04-01,,Active,75000,18937.5,2023,6,2023-06,1.0
|
||||
AcmeSaaS Inc.,EMP1000,Engineering,Engineering Manager,2022-03-04,,Active,180000,102900.0,2023,7,2023-07,1.0
|
||||
AcmeSaaS Inc.,EMP1001,Engineering,Senior Engineer,2022-04-02,,Active,160000,95200.0,2023,7,2023-07,1.0
|
||||
AcmeSaaS Inc.,EMP1002,Engineering,Senior Engineer,2022-11-08,,Active,160000,95200.0,2023,7,2023-07,1.0
|
||||
AcmeSaaS Inc.,EMP1003,Engineering,Software Engineer,2022-02-14,,Active,120000,68600.0,2023,7,2023-07,1.0
|
||||
AcmeSaaS Inc.,EMP1004,Sales,SDR,2022-08-04,,Active,65000,38675.0,2023,7,2023-07,1.0
|
||||
AcmeSaaS Inc.,EMP1005,Sales,SDR,2022-03-01,,Active,65000,37158.33,2023,7,2023-07,1.0
|
||||
AcmeSaaS Inc.,EMP1006,Sales,SDR,2022-03-14,,Active,65000,37916.67,2023,7,2023-07,1.0
|
||||
AcmeSaaS Inc.,EMP1007,Marketing,Content Strategist,2022-08-21,,Active,80000,47133.33,2023,7,2023-07,1.0
|
||||
AcmeSaaS Inc.,EMP1008,Marketing,Marketing Manager,2022-05-11,,Active,110000,64808.33,2023,7,2023-07,1.0
|
||||
AcmeSaaS Inc.,EMP1009,Marketing,Content Strategist,2022-11-02,,Active,80000,47600.0,2023,7,2023-07,1.0
|
||||
AcmeSaaS Inc.,EMP1010,Marketing,Marketing Manager,2022-11-19,,Active,110000,64808.33,2023,7,2023-07,1.0
|
||||
AcmeSaaS Inc.,EMP1011,Operations,Finance Analyst,2022-12-24,,Active,95000,55416.67,2023,7,2023-07,1.0
|
||||
AcmeSaaS Inc.,EMP1012,Operations,Operations Manager,2022-03-14,,Active,115000,68425.0,2023,7,2023-07,1.0
|
||||
AcmeSaaS Inc.,EMP1013,Sales,Account Executive,2023-01-01,,Active,90000,52500.0,2023,7,2023-07,0.5
|
||||
AcmeSaaS Inc.,EMP1014,Operations,Customer Success,2023-04-01,,Active,75000,25250.0,2023,7,2023-07,1.0
|
||||
AcmeSaaS Inc.,EMP1015,Engineering,Software Engineer,2023-07-01,,Active,120000,10200.0,2023,7,2023-07,0.5
|
||||
AcmeSaaS Inc.,EMP1000,Engineering,Engineering Manager,2022-03-04,,Active,180000,121200.0,2023,8,2023-08,1.0
|
||||
AcmeSaaS Inc.,EMP1001,Engineering,Senior Engineer,2022-04-02,,Active,160000,106666.67,2023,8,2023-08,1.0
|
||||
AcmeSaaS Inc.,EMP1002,Engineering,Senior Engineer,2022-11-08,,Active,160000,107733.33,2023,8,2023-08,1.0
|
||||
AcmeSaaS Inc.,EMP1003,Engineering,Software Engineer,2022-02-14,,Active,120000,79200.0,2023,8,2023-08,1.0
|
||||
AcmeSaaS Inc.,EMP1004,Sales,SDR,2022-08-04,,Active,65000,42900.0,2023,8,2023-08,1.0
|
||||
AcmeSaaS Inc.,EMP1005,Sales,SDR,2022-03-01,,Active,65000,43333.33,2023,8,2023-08,1.0
|
||||
AcmeSaaS Inc.,EMP1006,Sales,SDR,2022-03-14,,Active,65000,43333.33,2023,8,2023-08,1.0
|
||||
AcmeSaaS Inc.,EMP1007,Marketing,Content Strategist,2022-08-21,,Active,80000,53866.67,2023,8,2023-08,1.0
|
||||
AcmeSaaS Inc.,EMP1008,Marketing,Marketing Manager,2022-05-11,,Active,110000,72600.0,2023,8,2023-08,1.0
|
||||
AcmeSaaS Inc.,EMP1009,Marketing,Content Strategist,2022-11-02,,Active,80000,52266.67,2023,8,2023-08,1.0
|
||||
AcmeSaaS Inc.,EMP1010,Marketing,Marketing Manager,2022-11-19,,Active,110000,71866.67,2023,8,2023-08,1.0
|
||||
AcmeSaaS Inc.,EMP1011,Operations,Finance Analyst,2022-12-24,,Active,95000,62700.0,2023,8,2023-08,1.0
|
||||
AcmeSaaS Inc.,EMP1012,Operations,Operations Manager,2022-03-14,,Active,115000,77433.33,2023,8,2023-08,1.0
|
||||
AcmeSaaS Inc.,EMP1013,Sales,Account Executive,2023-01-01,,Active,90000,60000.0,2023,8,2023-08,0.5
|
||||
AcmeSaaS Inc.,EMP1014,Operations,Customer Success,2023-04-01,,Active,75000,31250.0,2023,8,2023-08,1.0
|
||||
AcmeSaaS Inc.,EMP1015,Engineering,Software Engineer,2023-07-01,,Active,120000,20200.0,2023,8,2023-08,0.5
|
||||
AcmeSaaS Inc.,EMP1000,Engineering,Engineering Manager,2022-03-04,,Active,180000,133650.0,2023,9,2023-09,1.0
|
||||
AcmeSaaS Inc.,EMP1001,Engineering,Senior Engineer,2022-04-02,,Active,160000,122400.0,2023,9,2023-09,1.0
|
||||
AcmeSaaS Inc.,EMP1002,Engineering,Senior Engineer,2022-11-08,,Active,160000,121200.0,2023,9,2023-09,1.0
|
||||
AcmeSaaS Inc.,EMP1003,Engineering,Software Engineer,2022-02-14,,Active,120000,88200.0,2023,9,2023-09,1.0
|
||||
AcmeSaaS Inc.,EMP1004,Sales,SDR,2022-08-04,,Active,65000,49237.5,2023,9,2023-09,1.0
|
||||
AcmeSaaS Inc.,EMP1005,Sales,SDR,2022-03-01,,Active,65000,48750.0,2023,9,2023-09,1.0
|
||||
AcmeSaaS Inc.,EMP1006,Sales,SDR,2022-03-14,,Active,65000,49237.5,2023,9,2023-09,1.0
|
||||
AcmeSaaS Inc.,EMP1007,Marketing,Content Strategist,2022-08-21,,Active,80000,61200.0,2023,9,2023-09,1.0
|
||||
AcmeSaaS Inc.,EMP1008,Marketing,Marketing Manager,2022-05-11,,Active,110000,80850.0,2023,9,2023-09,1.0
|
||||
AcmeSaaS Inc.,EMP1009,Marketing,Content Strategist,2022-11-02,,Active,80000,59400.0,2023,9,2023-09,1.0
|
||||
AcmeSaaS Inc.,EMP1010,Marketing,Marketing Manager,2022-11-19,,Active,110000,80850.0,2023,9,2023-09,1.0
|
||||
AcmeSaaS Inc.,EMP1011,Operations,Finance Analyst,2022-12-24,,Active,95000,69825.0,2023,9,2023-09,1.0
|
||||
AcmeSaaS Inc.,EMP1012,Operations,Operations Manager,2022-03-14,,Active,115000,86250.0,2023,9,2023-09,1.0
|
||||
AcmeSaaS Inc.,EMP1013,Sales,Account Executive,2023-01-01,,Active,90000,68175.0,2023,9,2023-09,0.5
|
||||
AcmeSaaS Inc.,EMP1014,Operations,Customer Success,2023-04-01,,Active,75000,36750.0,2023,9,2023-09,1.0
|
||||
AcmeSaaS Inc.,EMP1015,Engineering,Software Engineer,2023-07-01,,Active,120000,30300.0,2023,9,2023-09,0.5
|
||||
AcmeSaaS Inc.,EMP1000,Engineering,Engineering Manager,2022-03-04,,Active,180000,148500.0,2023,10,2023-10,1.0
|
||||
AcmeSaaS Inc.,EMP1001,Engineering,Senior Engineer,2022-04-02,,Active,160000,134666.67,2023,10,2023-10,1.0
|
||||
AcmeSaaS Inc.,EMP1002,Engineering,Senior Engineer,2022-11-08,,Active,160000,130666.67,2023,10,2023-10,1.0
|
||||
AcmeSaaS Inc.,EMP1003,Engineering,Software Engineer,2022-02-14,,Active,120000,100000.0,2023,10,2023-10,1.0
|
||||
AcmeSaaS Inc.,EMP1004,Sales,SDR,2022-08-04,,Active,65000,53625.0,2023,10,2023-10,1.0
|
||||
AcmeSaaS Inc.,EMP1005,Sales,SDR,2022-03-01,,Active,65000,54708.33,2023,10,2023-10,1.0
|
||||
AcmeSaaS Inc.,EMP1006,Sales,SDR,2022-03-14,,Active,65000,54708.33,2023,10,2023-10,1.0
|
||||
AcmeSaaS Inc.,EMP1007,Marketing,Content Strategist,2022-08-21,,Active,80000,66000.0,2023,10,2023-10,1.0
|
||||
AcmeSaaS Inc.,EMP1008,Marketing,Marketing Manager,2022-05-11,,Active,110000,91666.67,2023,10,2023-10,1.0
|
||||
AcmeSaaS Inc.,EMP1009,Marketing,Content Strategist,2022-11-02,,Active,80000,67333.33,2023,10,2023-10,1.0
|
||||
AcmeSaaS Inc.,EMP1010,Marketing,Marketing Manager,2022-11-19,,Active,110000,90750.0,2023,10,2023-10,1.0
|
||||
AcmeSaaS Inc.,EMP1011,Operations,Finance Analyst,2022-12-24,,Active,95000,78375.0,2023,10,2023-10,1.0
|
||||
AcmeSaaS Inc.,EMP1012,Operations,Operations Manager,2022-03-14,,Active,115000,94875.0,2023,10,2023-10,1.0
|
||||
AcmeSaaS Inc.,EMP1013,Sales,Account Executive,2023-01-01,,Active,90000,75750.0,2023,10,2023-10,0.5
|
||||
AcmeSaaS Inc.,EMP1014,Operations,Customer Success,2023-04-01,,Active,75000,43312.5,2023,10,2023-10,1.0
|
||||
AcmeSaaS Inc.,EMP1015,Engineering,Software Engineer,2023-07-01,,Active,120000,39600.0,2023,10,2023-10,0.5
|
||||
AcmeSaaS Inc.,EMP1000,Engineering,Engineering Manager,2022-03-04,,Active,180000,166650.0,2023,11,2023-11,1.0
|
||||
AcmeSaaS Inc.,EMP1001,Engineering,Senior Engineer,2022-04-02,,Active,160000,146666.67,2023,11,2023-11,1.0
|
||||
AcmeSaaS Inc.,EMP1002,Engineering,Senior Engineer,2022-11-08,,Active,160000,146666.67,2023,11,2023-11,1.0
|
||||
AcmeSaaS Inc.,EMP1003,Engineering,Software Engineer,2022-02-14,,Active,120000,112200.0,2023,11,2023-11,1.0
|
||||
AcmeSaaS Inc.,EMP1004,Sales,SDR,2022-08-04,,Active,65000,60775.0,2023,11,2023-11,1.0
|
||||
AcmeSaaS Inc.,EMP1005,Sales,SDR,2022-03-01,2023-11-01,Terminated,65000,60179.17,2023,11,2023-11,1.0
|
||||
AcmeSaaS Inc.,EMP1006,Sales,SDR,2022-03-14,,Active,65000,60179.17,2023,11,2023-11,1.0
|
||||
AcmeSaaS Inc.,EMP1007,Marketing,Content Strategist,2022-08-21,,Active,80000,72600.0,2023,11,2023-11,1.0
|
||||
AcmeSaaS Inc.,EMP1008,Marketing,Marketing Manager,2022-05-11,,Active,110000,100833.33,2023,11,2023-11,1.0
|
||||
AcmeSaaS Inc.,EMP1009,Marketing,Content Strategist,2022-11-02,,Active,80000,72600.0,2023,11,2023-11,1.0
|
||||
AcmeSaaS Inc.,EMP1010,Marketing,Marketing Manager,2022-11-19,,Active,110000,100833.33,2023,11,2023-11,1.0
|
||||
AcmeSaaS Inc.,EMP1011,Operations,Finance Analyst,2022-12-24,,Active,95000,87954.17,2023,11,2023-11,1.0
|
||||
AcmeSaaS Inc.,EMP1012,Operations,Operations Manager,2022-03-14,,Active,115000,105416.67,2023,11,2023-11,1.0
|
||||
AcmeSaaS Inc.,EMP1013,Sales,Account Executive,2023-01-01,,Active,90000,83325.0,2023,11,2023-11,0.5
|
||||
AcmeSaaS Inc.,EMP1014,Operations,Customer Success,2023-04-01,,Active,75000,49500.0,2023,11,2023-11,1.0
|
||||
AcmeSaaS Inc.,EMP1015,Engineering,Software Engineer,2023-07-01,,Active,120000,50500.0,2023,11,2023-11,0.5
|
||||
AcmeSaaS Inc.,EMP1000,Engineering,Engineering Manager,2022-03-04,,Active,180000,178200.0,2023,12,2023-12,1.0
|
||||
AcmeSaaS Inc.,EMP1001,Engineering,Senior Engineer,2022-04-02,,Active,160000,158400.0,2023,12,2023-12,1.0
|
||||
AcmeSaaS Inc.,EMP1002,Engineering,Senior Engineer,2022-11-08,,Active,160000,158400.0,2023,12,2023-12,1.0
|
||||
AcmeSaaS Inc.,EMP1003,Engineering,Software Engineer,2022-02-14,,Active,120000,121200.0,2023,12,2023-12,1.0
|
||||
AcmeSaaS Inc.,EMP1004,Sales,SDR,2022-08-04,,Active,65000,66300.0,2023,12,2023-12,1.0
|
||||
AcmeSaaS Inc.,EMP1005,Sales,SDR,2022-03-01,2023-11-01,Terminated,65000,65650.0,2023,12,2023-12,1.0
|
||||
AcmeSaaS Inc.,EMP1006,Sales,SDR,2022-03-14,,Active,65000,63700.0,2023,12,2023-12,1.0
|
||||
AcmeSaaS Inc.,EMP1007,Marketing,Content Strategist,2022-08-21,,Active,80000,80800.0,2023,12,2023-12,1.0
|
||||
AcmeSaaS Inc.,EMP1008,Marketing,Marketing Manager,2022-05-11,,Active,110000,108900.0,2023,12,2023-12,1.0
|
||||
AcmeSaaS Inc.,EMP1009,Marketing,Content Strategist,2022-11-02,,Active,80000,79200.0,2023,12,2023-12,1.0
|
||||
AcmeSaaS Inc.,EMP1010,Marketing,Marketing Manager,2022-11-19,,Active,110000,111100.0,2023,12,2023-12,1.0
|
||||
AcmeSaaS Inc.,EMP1011,Operations,Finance Analyst,2022-12-24,,Active,95000,95000.0,2023,12,2023-12,1.0
|
||||
AcmeSaaS Inc.,EMP1012,Operations,Operations Manager,2022-03-14,,Active,115000,116150.0,2023,12,2023-12,1.0
|
||||
AcmeSaaS Inc.,EMP1013,Sales,Account Executive,2023-01-01,,Active,90000,89100.0,2023,12,2023-12,0.5
|
||||
AcmeSaaS Inc.,EMP1014,Operations,Customer Success,2023-04-01,,Active,75000,56812.5,2023,12,2023-12,1.0
|
||||
AcmeSaaS Inc.,EMP1015,Engineering,Software Engineer,2023-07-01,,Active,120000,60600.0,2023,12,2023-12,0.5
|
||||
AcmeSaaS Inc.,EMP1016,Engineering,DevOps Engineer,2023-12-01,,Active,130000,10833.33,2023,12,2023-12,0.5
|
||||
AcmeSaaS Inc.,EMP1000,Engineering,Engineering Manager,2022-03-04,,Active,180000,14850.0,2024,1,2024-01,1.0
|
||||
AcmeSaaS Inc.,EMP1001,Engineering,Senior Engineer,2022-04-02,,Active,160000,13600.0,2024,1,2024-01,1.0
|
||||
AcmeSaaS Inc.,EMP1002,Engineering,Senior Engineer,2022-11-08,,Active,160000,13200.0,2024,1,2024-01,1.0
|
||||
AcmeSaaS Inc.,EMP1003,Engineering,Software Engineer,2022-02-14,,Active,120000,9900.0,2024,1,2024-01,1.0
|
||||
AcmeSaaS Inc.,EMP1004,Sales,SDR,2022-08-04,,Active,65000,5470.83,2024,1,2024-01,1.0
|
||||
AcmeSaaS Inc.,EMP1005,Sales,SDR,2022-03-01,2023-11-01,Terminated,65000,5470.83,2024,1,2024-01,1.0
|
||||
AcmeSaaS Inc.,EMP1006,Sales,SDR,2022-03-14,,Active,65000,5470.83,2024,1,2024-01,1.0
|
||||
AcmeSaaS Inc.,EMP1007,Marketing,Content Strategist,2022-08-21,,Active,80000,6800.0,2024,1,2024-01,1.0
|
||||
AcmeSaaS Inc.,EMP1008,Marketing,Marketing Manager,2022-05-11,,Active,110000,9258.33,2024,1,2024-01,1.0
|
||||
AcmeSaaS Inc.,EMP1009,Marketing,Content Strategist,2022-11-02,,Active,80000,6733.33,2024,1,2024-01,1.0
|
||||
AcmeSaaS Inc.,EMP1010,Marketing,Marketing Manager,2022-11-19,,Active,110000,9166.67,2024,1,2024-01,1.0
|
||||
AcmeSaaS Inc.,EMP1011,Operations,Finance Analyst,2022-12-24,2024-01-01,Terminated,95000,7837.5,2024,1,2024-01,1.0
|
||||
AcmeSaaS Inc.,EMP1012,Operations,Operations Manager,2022-03-14,,Active,115000,9487.5,2024,1,2024-01,1.0
|
||||
AcmeSaaS Inc.,EMP1013,Sales,Account Executive,2023-01-01,,Active,90000,7425.0,2024,1,2024-01,0.5
|
||||
AcmeSaaS Inc.,EMP1014,Operations,Customer Success,2023-04-01,,Active,75000,6250.0,2024,1,2024-01,1.0
|
||||
AcmeSaaS Inc.,EMP1015,Engineering,Software Engineer,2023-07-01,,Active,120000,10100.0,2024,1,2024-01,0.5
|
||||
AcmeSaaS Inc.,EMP1016,Engineering,DevOps Engineer,2023-12-01,,Active,130000,10725.0,2024,1,2024-01,0.5
|
||||
AcmeSaaS Inc.,EMP1000,Engineering,Engineering Manager,2022-03-04,,Active,180000,29400.0,2024,2,2024-02,1.0
|
||||
AcmeSaaS Inc.,EMP1001,Engineering,Senior Engineer,2022-04-02,,Active,160000,27200.0,2024,2,2024-02,1.0
|
||||
AcmeSaaS Inc.,EMP1002,Engineering,Senior Engineer,2022-11-08,,Active,160000,26666.67,2024,2,2024-02,1.0
|
||||
AcmeSaaS Inc.,EMP1003,Engineering,Software Engineer,2022-02-14,,Active,120000,20200.0,2024,2,2024-02,1.0
|
||||
AcmeSaaS Inc.,EMP1004,Sales,SDR,2022-08-04,,Active,65000,11050.0,2024,2,2024-02,1.0
|
||||
AcmeSaaS Inc.,EMP1005,Sales,SDR,2022-03-01,2023-11-01,Terminated,65000,10725.0,2024,2,2024-02,1.0
|
||||
AcmeSaaS Inc.,EMP1006,Sales,SDR,2022-03-14,,Active,65000,10833.33,2024,2,2024-02,1.0
|
||||
AcmeSaaS Inc.,EMP1007,Marketing,Content Strategist,2022-08-21,,Active,80000,13333.33,2024,2,2024-02,1.0
|
||||
AcmeSaaS Inc.,EMP1008,Marketing,Marketing Manager,2022-05-11,2024-02-01,Terminated,110000,18150.0,2024,2,2024-02,1.0
|
||||
AcmeSaaS Inc.,EMP1009,Marketing,Content Strategist,2022-11-02,,Active,80000,13200.0,2024,2,2024-02,1.0
|
||||
AcmeSaaS Inc.,EMP1010,Marketing,Marketing Manager,2022-11-19,,Active,110000,18150.0,2024,2,2024-02,1.0
|
||||
AcmeSaaS Inc.,EMP1011,Operations,Finance Analyst,2022-12-24,2024-01-01,Terminated,95000,16150.0,2024,2,2024-02,1.0
|
||||
AcmeSaaS Inc.,EMP1012,Operations,Operations Manager,2022-03-14,,Active,115000,19166.67,2024,2,2024-02,1.0
|
||||
AcmeSaaS Inc.,EMP1013,Sales,Account Executive,2023-01-01,,Active,90000,15000.0,2024,2,2024-02,0.5
|
||||
AcmeSaaS Inc.,EMP1014,Operations,Customer Success,2023-04-01,,Active,75000,12375.0,2024,2,2024-02,1.0
|
||||
AcmeSaaS Inc.,EMP1015,Engineering,Software Engineer,2023-07-01,,Active,120000,19800.0,2024,2,2024-02,0.5
|
||||
AcmeSaaS Inc.,EMP1016,Engineering,DevOps Engineer,2023-12-01,,Active,130000,21666.67,2024,2,2024-02,0.5
|
||||
AcmeSaaS Inc.,EMP1000,Engineering,Engineering Manager,2022-03-04,,Active,180000,45450.0,2024,3,2024-03,1.0
|
||||
AcmeSaaS Inc.,EMP1001,Engineering,Senior Engineer,2022-04-02,,Active,160000,40800.0,2024,3,2024-03,1.0
|
||||
AcmeSaaS Inc.,EMP1002,Engineering,Senior Engineer,2022-11-08,,Active,160000,40400.0,2024,3,2024-03,1.0
|
||||
AcmeSaaS Inc.,EMP1003,Engineering,Software Engineer,2022-02-14,,Active,120000,30000.0,2024,3,2024-03,1.0
|
||||
AcmeSaaS Inc.,EMP1004,Sales,SDR,2022-08-04,,Active,65000,16087.5,2024,3,2024-03,1.0
|
||||
AcmeSaaS Inc.,EMP1005,Sales,SDR,2022-03-01,2023-11-01,Terminated,65000,16412.5,2024,3,2024-03,1.0
|
||||
AcmeSaaS Inc.,EMP1006,Sales,SDR,2022-03-14,,Active,65000,16087.5,2024,3,2024-03,1.0
|
||||
AcmeSaaS Inc.,EMP1007,Marketing,Content Strategist,2022-08-21,,Active,80000,20200.0,2024,3,2024-03,1.0
|
||||
AcmeSaaS Inc.,EMP1008,Marketing,Marketing Manager,2022-05-11,2024-02-01,Terminated,110000,27500.0,2024,3,2024-03,1.0
|
||||
AcmeSaaS Inc.,EMP1009,Marketing,Content Strategist,2022-11-02,,Active,80000,20400.0,2024,3,2024-03,1.0
|
||||
AcmeSaaS Inc.,EMP1010,Marketing,Marketing Manager,2022-11-19,,Active,110000,27500.0,2024,3,2024-03,1.0
|
||||
AcmeSaaS Inc.,EMP1011,Operations,Finance Analyst,2022-12-24,2024-01-01,Terminated,95000,23987.5,2024,3,2024-03,1.0
|
||||
AcmeSaaS Inc.,EMP1012,Operations,Operations Manager,2022-03-14,,Active,115000,28175.0,2024,3,2024-03,1.0
|
||||
AcmeSaaS Inc.,EMP1013,Sales,Account Executive,2023-01-01,,Active,90000,22050.0,2024,3,2024-03,0.5
|
||||
AcmeSaaS Inc.,EMP1014,Operations,Customer Success,2023-04-01,,Active,75000,18937.5,2024,3,2024-03,1.0
|
||||
AcmeSaaS Inc.,EMP1015,Engineering,Software Engineer,2023-07-01,,Active,120000,30600.0,2024,3,2024-03,0.5
|
||||
AcmeSaaS Inc.,EMP1016,Engineering,DevOps Engineer,2023-12-01,,Active,130000,32825.0,2024,3,2024-03,0.5
|
||||
AcmeSaaS Inc.,EMP1017,Operations,Finance Analyst,2024-03-01,,Active,95000,7995.83,2024,3,2024-03,1.0
|
||||
AcmeSaaS Inc.,EMP1000,Engineering,Engineering Manager,2022-03-04,,Active,180000,59400.0,2024,4,2024-04,1.0
|
||||
AcmeSaaS Inc.,EMP1001,Engineering,Senior Engineer,2022-04-02,,Active,160000,53866.67,2024,4,2024-04,1.0
|
||||
AcmeSaaS Inc.,EMP1002,Engineering,Senior Engineer,2022-11-08,,Active,160000,52266.67,2024,4,2024-04,1.0
|
||||
AcmeSaaS Inc.,EMP1003,Engineering,Software Engineer,2022-02-14,,Active,120000,40000.0,2024,4,2024-04,1.0
|
||||
AcmeSaaS Inc.,EMP1004,Sales,SDR,2022-08-04,,Active,65000,21450.0,2024,4,2024-04,1.0
|
||||
AcmeSaaS Inc.,EMP1005,Sales,SDR,2022-03-01,2023-11-01,Terminated,65000,21666.67,2024,4,2024-04,1.0
|
||||
AcmeSaaS Inc.,EMP1006,Sales,SDR,2022-03-14,,Active,65000,21883.33,2024,4,2024-04,1.0
|
||||
AcmeSaaS Inc.,EMP1007,Marketing,Content Strategist,2022-08-21,,Active,80000,26666.67,2024,4,2024-04,1.0
|
||||
AcmeSaaS Inc.,EMP1008,Marketing,Marketing Manager,2022-05-11,2024-02-01,Terminated,110000,36666.67,2024,4,2024-04,1.0
|
||||
AcmeSaaS Inc.,EMP1009,Marketing,Content Strategist,2022-11-02,,Active,80000,26666.67,2024,4,2024-04,1.0
|
||||
AcmeSaaS Inc.,EMP1010,Marketing,Marketing Manager,2022-11-19,,Active,110000,35933.33,2024,4,2024-04,1.0
|
||||
AcmeSaaS Inc.,EMP1011,Operations,Finance Analyst,2022-12-24,2024-01-01,Terminated,95000,31666.67,2024,4,2024-04,1.0
|
||||
AcmeSaaS Inc.,EMP1012,Operations,Operations Manager,2022-03-14,,Active,115000,37566.67,2024,4,2024-04,1.0
|
||||
AcmeSaaS Inc.,EMP1013,Sales,Account Executive,2023-01-01,,Active,90000,30000.0,2024,4,2024-04,0.5
|
||||
AcmeSaaS Inc.,EMP1014,Operations,Customer Success,2023-04-01,,Active,75000,24750.0,2024,4,2024-04,1.0
|
||||
AcmeSaaS Inc.,EMP1015,Engineering,Software Engineer,2023-07-01,,Active,120000,40800.0,2024,4,2024-04,0.5
|
||||
AcmeSaaS Inc.,EMP1016,Engineering,DevOps Engineer,2023-12-01,,Active,130000,42466.67,2024,4,2024-04,0.5
|
||||
AcmeSaaS Inc.,EMP1017,Operations,Finance Analyst,2024-03-01,,Active,95000,15991.67,2024,4,2024-04,1.0
|
||||
AcmeSaaS Inc.,EMP1000,Engineering,Engineering Manager,2022-03-04,,Active,180000,74250.0,2024,5,2024-05,1.0
|
||||
AcmeSaaS Inc.,EMP1001,Engineering,Senior Engineer,2022-04-02,,Active,160000,66666.67,2024,5,2024-05,1.0
|
||||
AcmeSaaS Inc.,EMP1002,Engineering,Senior Engineer,2022-11-08,,Active,160000,66000.0,2024,5,2024-05,1.0
|
||||
AcmeSaaS Inc.,EMP1003,Engineering,Software Engineer,2022-02-14,,Active,120000,49500.0,2024,5,2024-05,1.0
|
||||
AcmeSaaS Inc.,EMP1004,Sales,SDR,2022-08-04,,Active,65000,27354.17,2024,5,2024-05,1.0
|
||||
AcmeSaaS Inc.,EMP1005,Sales,SDR,2022-03-01,2023-11-01,Terminated,65000,26541.67,2024,5,2024-05,1.0
|
||||
AcmeSaaS Inc.,EMP1006,Sales,SDR,2022-03-14,,Active,65000,27625.0,2024,5,2024-05,1.0
|
||||
AcmeSaaS Inc.,EMP1007,Marketing,Content Strategist,2022-08-21,,Active,80000,33333.33,2024,5,2024-05,1.0
|
||||
AcmeSaaS Inc.,EMP1008,Marketing,Marketing Manager,2022-05-11,2024-02-01,Terminated,110000,45375.0,2024,5,2024-05,1.0
|
||||
AcmeSaaS Inc.,EMP1009,Marketing,Content Strategist,2022-11-02,,Active,80000,33666.67,2024,5,2024-05,1.0
|
||||
AcmeSaaS Inc.,EMP1010,Marketing,Marketing Manager,2022-11-19,,Active,110000,45375.0,2024,5,2024-05,1.0
|
||||
AcmeSaaS Inc.,EMP1011,Operations,Finance Analyst,2022-12-24,2024-01-01,Terminated,95000,39979.17,2024,5,2024-05,1.0
|
||||
AcmeSaaS Inc.,EMP1012,Operations,Operations Manager,2022-03-14,,Active,115000,46958.33,2024,5,2024-05,1.0
|
||||
AcmeSaaS Inc.,EMP1013,Sales,Account Executive,2023-01-01,,Active,90000,38250.0,2024,5,2024-05,0.5
|
||||
AcmeSaaS Inc.,EMP1014,Operations,Customer Success,2023-04-01,,Active,75000,31562.5,2024,5,2024-05,1.0
|
||||
AcmeSaaS Inc.,EMP1015,Engineering,Software Engineer,2023-07-01,,Active,120000,49000.0,2024,5,2024-05,0.5
|
||||
AcmeSaaS Inc.,EMP1016,Engineering,DevOps Engineer,2023-12-01,,Active,130000,54166.67,2024,5,2024-05,0.5
|
||||
AcmeSaaS Inc.,EMP1017,Operations,Finance Analyst,2024-03-01,,Active,95000,23750.0,2024,5,2024-05,1.0
|
||||
AcmeSaaS Inc.,EMP1018,Sales,Account Executive,2024-05-01,,Active,90000,7500.0,2024,5,2024-05,1.0
|
||||
AcmeSaaS Inc.,EMP1000,Engineering,Engineering Manager,2022-03-04,,Active,180000,90900.0,2024,6,2024-06,1.0
|
||||
AcmeSaaS Inc.,EMP1001,Engineering,Senior Engineer,2022-04-02,,Active,160000,79200.0,2024,6,2024-06,1.0
|
||||
AcmeSaaS Inc.,EMP1002,Engineering,Senior Engineer,2022-11-08,,Active,160000,81600.0,2024,6,2024-06,1.0
|
||||
AcmeSaaS Inc.,EMP1003,Engineering,Software Engineer,2022-02-14,,Active,120000,61200.0,2024,6,2024-06,1.0
|
||||
AcmeSaaS Inc.,EMP1004,Sales,SDR,2022-08-04,,Active,65000,33150.0,2024,6,2024-06,1.0
|
||||
AcmeSaaS Inc.,EMP1005,Sales,SDR,2022-03-01,2023-11-01,Terminated,65000,33150.0,2024,6,2024-06,1.0
|
||||
AcmeSaaS Inc.,EMP1006,Sales,SDR,2022-03-14,,Active,65000,32175.0,2024,6,2024-06,1.0
|
||||
AcmeSaaS Inc.,EMP1007,Marketing,Content Strategist,2022-08-21,,Active,80000,40800.0,2024,6,2024-06,1.0
|
||||
AcmeSaaS Inc.,EMP1008,Marketing,Marketing Manager,2022-05-11,2024-02-01,Terminated,110000,55550.0,2024,6,2024-06,1.0
|
||||
AcmeSaaS Inc.,EMP1009,Marketing,Content Strategist,2022-11-02,,Active,80000,40000.0,2024,6,2024-06,1.0
|
||||
AcmeSaaS Inc.,EMP1010,Marketing,Marketing Manager,2022-11-19,,Active,110000,54450.0,2024,6,2024-06,1.0
|
||||
AcmeSaaS Inc.,EMP1011,Operations,Finance Analyst,2022-12-24,2024-01-01,Terminated,95000,48450.0,2024,6,2024-06,1.0
|
||||
AcmeSaaS Inc.,EMP1012,Operations,Operations Manager,2022-03-14,,Active,115000,58075.0,2024,6,2024-06,1.0
|
||||
AcmeSaaS Inc.,EMP1013,Sales,Account Executive,2023-01-01,,Active,90000,44550.0,2024,6,2024-06,0.5
|
||||
AcmeSaaS Inc.,EMP1014,Operations,Customer Success,2023-04-01,,Active,75000,37500.0,2024,6,2024-06,1.0
|
||||
AcmeSaaS Inc.,EMP1015,Engineering,Software Engineer,2023-07-01,,Active,120000,60600.0,2024,6,2024-06,0.5
|
||||
AcmeSaaS Inc.,EMP1016,Engineering,DevOps Engineer,2023-12-01,,Active,130000,64350.0,2024,6,2024-06,0.5
|
||||
AcmeSaaS Inc.,EMP1017,Operations,Finance Analyst,2024-03-01,,Active,95000,31983.33,2024,6,2024-06,1.0
|
||||
AcmeSaaS Inc.,EMP1018,Sales,Account Executive,2024-05-01,,Active,90000,14700.0,2024,6,2024-06,1.0
|
||||
AcmeSaaS Inc.,EMP1019,Sales,Account Executive,2024-06-01,,Active,90000,7425.0,2024,6,2024-06,0.5
|
||||
AcmeSaaS Inc.,EMP1000,Engineering,Engineering Manager,2022-03-04,,Active,180000,106050.0,2024,7,2024-07,1.0
|
||||
AcmeSaaS Inc.,EMP1001,Engineering,Senior Engineer,2022-04-02,,Active,160000,95200.0,2024,7,2024-07,1.0
|
||||
AcmeSaaS Inc.,EMP1002,Engineering,Senior Engineer,2022-11-08,,Active,160000,92400.0,2024,7,2024-07,1.0
|
||||
AcmeSaaS Inc.,EMP1003,Engineering,Software Engineer,2022-02-14,,Active,120000,70000.0,2024,7,2024-07,1.0
|
||||
AcmeSaaS Inc.,EMP1004,Sales,SDR,2022-08-04,,Active,65000,37916.67,2024,7,2024-07,1.0
|
||||
AcmeSaaS Inc.,EMP1005,Sales,SDR,2022-03-01,2023-11-01,Terminated,65000,38295.83,2024,7,2024-07,1.0
|
||||
AcmeSaaS Inc.,EMP1006,Sales,SDR,2022-03-14,,Active,65000,38295.83,2024,7,2024-07,1.0
|
||||
AcmeSaaS Inc.,EMP1007,Marketing,Content Strategist,2022-08-21,,Active,80000,46666.67,2024,7,2024-07,1.0
|
||||
AcmeSaaS Inc.,EMP1008,Marketing,Marketing Manager,2022-05-11,2024-02-01,Terminated,110000,64166.67,2024,7,2024-07,1.0
|
||||
AcmeSaaS Inc.,EMP1009,Marketing,Content Strategist,2022-11-02,,Active,80000,47133.33,2024,7,2024-07,1.0
|
||||
AcmeSaaS Inc.,EMP1010,Marketing,Marketing Manager,2022-11-19,,Active,110000,64808.33,2024,7,2024-07,1.0
|
||||
AcmeSaaS Inc.,EMP1011,Operations,Finance Analyst,2022-12-24,2024-01-01,Terminated,95000,55970.83,2024,7,2024-07,1.0
|
||||
AcmeSaaS Inc.,EMP1012,Operations,Operations Manager,2022-03-14,,Active,115000,65741.67,2024,7,2024-07,1.0
|
||||
AcmeSaaS Inc.,EMP1013,Sales,Account Executive,2023-01-01,,Active,90000,53025.0,2024,7,2024-07,0.5
|
||||
AcmeSaaS Inc.,EMP1014,Operations,Customer Success,2023-04-01,,Active,75000,43312.5,2024,7,2024-07,1.0
|
||||
AcmeSaaS Inc.,EMP1015,Engineering,Software Engineer,2023-07-01,,Active,120000,68600.0,2024,7,2024-07,0.5
|
||||
AcmeSaaS Inc.,EMP1016,Engineering,DevOps Engineer,2023-12-01,,Active,130000,75075.0,2024,7,2024-07,0.5
|
||||
AcmeSaaS Inc.,EMP1017,Operations,Finance Analyst,2024-03-01,,Active,95000,38791.67,2024,7,2024-07,1.0
|
||||
AcmeSaaS Inc.,EMP1018,Sales,Account Executive,2024-05-01,,Active,90000,22500.0,2024,7,2024-07,1.0
|
||||
AcmeSaaS Inc.,EMP1019,Sales,Account Executive,2024-06-01,,Active,90000,15000.0,2024,7,2024-07,0.5
|
||||
AcmeSaaS Inc.,EMP1000,Engineering,Engineering Manager,2022-03-04,,Active,180000,120000.0,2024,8,2024-08,1.0
|
||||
AcmeSaaS Inc.,EMP1001,Engineering,Senior Engineer,2022-04-02,,Active,160000,105600.0,2024,8,2024-08,1.0
|
||||
AcmeSaaS Inc.,EMP1002,Engineering,Senior Engineer,2022-11-08,,Active,160000,106666.67,2024,8,2024-08,1.0
|
||||
AcmeSaaS Inc.,EMP1003,Engineering,Software Engineer,2022-02-14,,Active,120000,80000.0,2024,8,2024-08,1.0
|
||||
AcmeSaaS Inc.,EMP1004,Sales,SDR,2022-08-04,,Active,65000,42900.0,2024,8,2024-08,1.0
|
||||
AcmeSaaS Inc.,EMP1005,Sales,SDR,2022-03-01,2023-11-01,Terminated,65000,43333.33,2024,8,2024-08,1.0
|
||||
AcmeSaaS Inc.,EMP1006,Sales,SDR,2022-03-14,,Active,65000,43333.33,2024,8,2024-08,1.0
|
||||
AcmeSaaS Inc.,EMP1007,Marketing,Content Strategist,2022-08-21,,Active,80000,53866.67,2024,8,2024-08,1.0
|
||||
AcmeSaaS Inc.,EMP1008,Marketing,Marketing Manager,2022-05-11,2024-02-01,Terminated,110000,72600.0,2024,8,2024-08,1.0
|
||||
AcmeSaaS Inc.,EMP1009,Marketing,Content Strategist,2022-11-02,,Active,80000,52800.0,2024,8,2024-08,1.0
|
||||
AcmeSaaS Inc.,EMP1010,Marketing,Marketing Manager,2022-11-19,,Active,110000,71866.67,2024,8,2024-08,1.0
|
||||
AcmeSaaS Inc.,EMP1011,Operations,Finance Analyst,2022-12-24,2024-01-01,Terminated,95000,62700.0,2024,8,2024-08,1.0
|
||||
AcmeSaaS Inc.,EMP1012,Operations,Operations Manager,2022-03-14,,Active,115000,76666.67,2024,8,2024-08,1.0
|
||||
AcmeSaaS Inc.,EMP1013,Sales,Account Executive,2023-01-01,,Active,90000,60600.0,2024,8,2024-08,0.5
|
||||
AcmeSaaS Inc.,EMP1014,Operations,Customer Success,2023-04-01,,Active,75000,49500.0,2024,8,2024-08,1.0
|
||||
AcmeSaaS Inc.,EMP1015,Engineering,Software Engineer,2023-07-01,,Active,120000,80000.0,2024,8,2024-08,0.5
|
||||
AcmeSaaS Inc.,EMP1016,Engineering,DevOps Engineer,2023-12-01,,Active,130000,85800.0,2024,8,2024-08,0.5
|
||||
AcmeSaaS Inc.,EMP1017,Operations,Finance Analyst,2024-03-01,,Active,95000,46550.0,2024,8,2024-08,1.0
|
||||
AcmeSaaS Inc.,EMP1018,Sales,Account Executive,2024-05-01,,Active,90000,30600.0,2024,8,2024-08,1.0
|
||||
AcmeSaaS Inc.,EMP1019,Sales,Account Executive,2024-06-01,,Active,90000,22500.0,2024,8,2024-08,0.5
|
||||
AcmeSaaS Inc.,EMP1020,Marketing,Marketing Manager,2024-08-01,,Active,110000,9258.33,2024,8,2024-08,1.0
|
||||
AcmeSaaS Inc.,EMP1000,Engineering,Engineering Manager,2022-03-04,,Active,180000,135000.0,2024,9,2024-09,1.0
|
||||
AcmeSaaS Inc.,EMP1001,Engineering,Senior Engineer,2022-04-02,,Active,160000,120000.0,2024,9,2024-09,1.0
|
||||
AcmeSaaS Inc.,EMP1002,Engineering,Senior Engineer,2022-11-08,,Active,160000,120000.0,2024,9,2024-09,1.0
|
||||
AcmeSaaS Inc.,EMP1003,Engineering,Software Engineer,2022-02-14,,Active,120000,88200.0,2024,9,2024-09,1.0
|
||||
AcmeSaaS Inc.,EMP1004,Sales,SDR,2022-08-04,,Active,65000,47775.0,2024,9,2024-09,1.0
|
||||
AcmeSaaS Inc.,EMP1005,Sales,SDR,2022-03-01,2023-11-01,Terminated,65000,48750.0,2024,9,2024-09,1.0
|
||||
AcmeSaaS Inc.,EMP1006,Sales,SDR,2022-03-14,,Active,65000,48750.0,2024,9,2024-09,1.0
|
||||
AcmeSaaS Inc.,EMP1007,Marketing,Content Strategist,2022-08-21,,Active,80000,60600.0,2024,9,2024-09,1.0
|
||||
AcmeSaaS Inc.,EMP1008,Marketing,Marketing Manager,2022-05-11,2024-02-01,Terminated,110000,83325.0,2024,9,2024-09,1.0
|
||||
AcmeSaaS Inc.,EMP1009,Marketing,Content Strategist,2022-11-02,,Active,80000,58800.0,2024,9,2024-09,1.0
|
||||
AcmeSaaS Inc.,EMP1010,Marketing,Marketing Manager,2022-11-19,,Active,110000,82500.0,2024,9,2024-09,1.0
|
||||
AcmeSaaS Inc.,EMP1011,Operations,Finance Analyst,2022-12-24,2024-01-01,Terminated,95000,71962.5,2024,9,2024-09,1.0
|
||||
AcmeSaaS Inc.,EMP1012,Operations,Operations Manager,2022-03-14,,Active,115000,87112.5,2024,9,2024-09,1.0
|
||||
AcmeSaaS Inc.,EMP1013,Sales,Account Executive,2023-01-01,,Active,90000,67500.0,2024,9,2024-09,0.5
|
||||
AcmeSaaS Inc.,EMP1014,Operations,Customer Success,2023-04-01,,Active,75000,57375.0,2024,9,2024-09,1.0
|
||||
AcmeSaaS Inc.,EMP1015,Engineering,Software Engineer,2023-07-01,,Active,120000,89100.0,2024,9,2024-09,0.5
|
||||
AcmeSaaS Inc.,EMP1016,Engineering,DevOps Engineer,2023-12-01,,Active,130000,97500.0,2024,9,2024-09,0.5
|
||||
AcmeSaaS Inc.,EMP1017,Operations,Finance Analyst,2024-03-01,,Active,95000,55970.83,2024,9,2024-09,1.0
|
||||
AcmeSaaS Inc.,EMP1018,Sales,Account Executive,2024-05-01,,Active,90000,37125.0,2024,9,2024-09,1.0
|
||||
AcmeSaaS Inc.,EMP1019,Sales,Account Executive,2024-06-01,,Active,90000,29700.0,2024,9,2024-09,0.5
|
||||
AcmeSaaS Inc.,EMP1020,Marketing,Marketing Manager,2024-08-01,,Active,110000,18150.0,2024,9,2024-09,1.0
|
||||
AcmeSaaS Inc.,EMP1000,Engineering,Engineering Manager,2022-03-04,,Active,180000,148500.0,2024,10,2024-10,1.0
|
||||
AcmeSaaS Inc.,EMP1001,Engineering,Senior Engineer,2022-04-02,,Active,160000,132000.0,2024,10,2024-10,1.0
|
||||
AcmeSaaS Inc.,EMP1002,Engineering,Senior Engineer,2022-11-08,,Active,160000,134666.67,2024,10,2024-10,1.0
|
||||
AcmeSaaS Inc.,EMP1003,Engineering,Software Engineer,2022-02-14,,Active,120000,99000.0,2024,10,2024-10,1.0
|
||||
AcmeSaaS Inc.,EMP1004,Sales,SDR,2022-08-04,,Active,65000,55250.0,2024,10,2024-10,1.0
|
||||
AcmeSaaS Inc.,EMP1005,Sales,SDR,2022-03-01,2023-11-01,Terminated,65000,54708.33,2024,10,2024-10,1.0
|
||||
AcmeSaaS Inc.,EMP1006,Sales,SDR,2022-03-14,,Active,65000,54166.67,2024,10,2024-10,1.0
|
||||
AcmeSaaS Inc.,EMP1007,Marketing,Content Strategist,2022-08-21,,Active,80000,66666.67,2024,10,2024-10,1.0
|
||||
AcmeSaaS Inc.,EMP1008,Marketing,Marketing Manager,2022-05-11,2024-02-01,Terminated,110000,91666.67,2024,10,2024-10,1.0
|
||||
AcmeSaaS Inc.,EMP1009,Marketing,Content Strategist,2022-11-02,,Active,80000,66000.0,2024,10,2024-10,1.0
|
||||
AcmeSaaS Inc.,EMP1010,Marketing,Marketing Manager,2022-11-19,,Active,110000,89833.33,2024,10,2024-10,1.0
|
||||
AcmeSaaS Inc.,EMP1011,Operations,Finance Analyst,2022-12-24,2024-01-01,Terminated,95000,79166.67,2024,10,2024-10,1.0
|
||||
AcmeSaaS Inc.,EMP1012,Operations,Operations Manager,2022-03-14,,Active,115000,96791.67,2024,10,2024-10,1.0
|
||||
AcmeSaaS Inc.,EMP1013,Sales,Account Executive,2023-01-01,,Active,90000,75000.0,2024,10,2024-10,0.5
|
||||
AcmeSaaS Inc.,EMP1014,Operations,Customer Success,2023-04-01,,Active,75000,63125.0,2024,10,2024-10,1.0
|
||||
AcmeSaaS Inc.,EMP1015,Engineering,Software Engineer,2023-07-01,,Active,120000,100000.0,2024,10,2024-10,0.5
|
||||
AcmeSaaS Inc.,EMP1016,Engineering,DevOps Engineer,2023-12-01,,Active,130000,108333.33,2024,10,2024-10,0.5
|
||||
AcmeSaaS Inc.,EMP1017,Operations,Finance Analyst,2024-03-01,,Active,95000,63966.67,2024,10,2024-10,1.0
|
||||
AcmeSaaS Inc.,EMP1018,Sales,Account Executive,2024-05-01,,Active,90000,45450.0,2024,10,2024-10,1.0
|
||||
AcmeSaaS Inc.,EMP1019,Sales,Account Executive,2024-06-01,,Active,90000,37500.0,2024,10,2024-10,0.5
|
||||
AcmeSaaS Inc.,EMP1020,Marketing,Marketing Manager,2024-08-01,,Active,110000,27225.0,2024,10,2024-10,1.0
|
||||
AcmeSaaS Inc.,EMP1000,Engineering,Engineering Manager,2022-03-04,,Active,180000,165000.0,2024,11,2024-11,1.0
|
||||
AcmeSaaS Inc.,EMP1001,Engineering,Senior Engineer,2022-04-02,,Active,160000,145200.0,2024,11,2024-11,1.0
|
||||
AcmeSaaS Inc.,EMP1002,Engineering,Senior Engineer,2022-11-08,,Active,160000,148133.33,2024,11,2024-11,1.0
|
||||
AcmeSaaS Inc.,EMP1003,Engineering,Software Engineer,2022-02-14,,Active,120000,111100.0,2024,11,2024-11,1.0
|
||||
AcmeSaaS Inc.,EMP1004,Sales,SDR,2022-08-04,,Active,65000,60775.0,2024,11,2024-11,1.0
|
||||
AcmeSaaS Inc.,EMP1005,Sales,SDR,2022-03-01,2023-11-01,Terminated,65000,60179.17,2024,11,2024-11,1.0
|
||||
AcmeSaaS Inc.,EMP1006,Sales,SDR,2022-03-14,,Active,65000,58987.5,2024,11,2024-11,1.0
|
||||
AcmeSaaS Inc.,EMP1007,Marketing,Content Strategist,2022-08-21,,Active,80000,72600.0,2024,11,2024-11,1.0
|
||||
AcmeSaaS Inc.,EMP1008,Marketing,Marketing Manager,2022-05-11,2024-02-01,Terminated,110000,101841.67,2024,11,2024-11,1.0
|
||||
AcmeSaaS Inc.,EMP1009,Marketing,Content Strategist,2022-11-02,,Active,80000,72600.0,2024,11,2024-11,1.0
|
||||
AcmeSaaS Inc.,EMP1010,Marketing,Marketing Manager,2022-11-19,,Active,110000,102850.0,2024,11,2024-11,1.0
|
||||
AcmeSaaS Inc.,EMP1011,Operations,Finance Analyst,2022-12-24,2024-01-01,Terminated,95000,86212.5,2024,11,2024-11,1.0
|
||||
AcmeSaaS Inc.,EMP1012,Operations,Operations Manager,2022-03-14,,Active,115000,107525.0,2024,11,2024-11,1.0
|
||||
AcmeSaaS Inc.,EMP1013,Sales,Account Executive,2023-01-01,,Active,90000,81675.0,2024,11,2024-11,0.5
|
||||
AcmeSaaS Inc.,EMP1014,Operations,Customer Success,2023-04-01,,Active,75000,69437.5,2024,11,2024-11,1.0
|
||||
AcmeSaaS Inc.,EMP1015,Engineering,Software Engineer,2023-07-01,,Active,120000,107800.0,2024,11,2024-11,0.5
|
||||
AcmeSaaS Inc.,EMP1016,Engineering,DevOps Engineer,2023-12-01,,Active,130000,120358.33,2024,11,2024-11,0.5
|
||||
AcmeSaaS Inc.,EMP1017,Operations,Finance Analyst,2024-03-01,,Active,95000,72675.0,2024,11,2024-11,1.0
|
||||
AcmeSaaS Inc.,EMP1018,Sales,Account Executive,2024-05-01,,Active,90000,51975.0,2024,11,2024-11,1.0
|
||||
AcmeSaaS Inc.,EMP1019,Sales,Account Executive,2024-06-01,,Active,90000,45450.0,2024,11,2024-11,0.5
|
||||
AcmeSaaS Inc.,EMP1020,Marketing,Marketing Manager,2024-08-01,,Active,110000,37033.33,2024,11,2024-11,1.0
|
||||
AcmeSaaS Inc.,EMP1021,Sales,Sales Manager,2024-11-01,,Active,140000,11433.33,2024,11,2024-11,1.0
|
||||
AcmeSaaS Inc.,EMP1000,Engineering,Engineering Manager,2022-03-04,,Active,180000,178200.0,2024,12,2024-12,1.0
|
||||
AcmeSaaS Inc.,EMP1001,Engineering,Senior Engineer,2022-04-02,,Active,160000,158400.0,2024,12,2024-12,1.0
|
||||
AcmeSaaS Inc.,EMP1002,Engineering,Senior Engineer,2022-11-08,,Active,160000,158400.0,2024,12,2024-12,1.0
|
||||
AcmeSaaS Inc.,EMP1003,Engineering,Software Engineer,2022-02-14,,Active,120000,120000.0,2024,12,2024-12,1.0
|
||||
AcmeSaaS Inc.,EMP1004,Sales,SDR,2022-08-04,,Active,65000,65000.0,2024,12,2024-12,1.0
|
||||
AcmeSaaS Inc.,EMP1005,Sales,SDR,2022-03-01,2023-11-01,Terminated,65000,65000.0,2024,12,2024-12,1.0
|
||||
AcmeSaaS Inc.,EMP1006,Sales,SDR,2022-03-14,,Active,65000,65000.0,2024,12,2024-12,1.0
|
||||
AcmeSaaS Inc.,EMP1007,Marketing,Content Strategist,2022-08-21,,Active,80000,80000.0,2024,12,2024-12,1.0
|
||||
AcmeSaaS Inc.,EMP1008,Marketing,Marketing Manager,2022-05-11,2024-02-01,Terminated,110000,110000.0,2024,12,2024-12,1.0
|
||||
AcmeSaaS Inc.,EMP1009,Marketing,Content Strategist,2022-11-02,,Active,80000,81600.0,2024,12,2024-12,1.0
|
||||
AcmeSaaS Inc.,EMP1010,Marketing,Marketing Manager,2022-11-19,,Active,110000,111100.0,2024,12,2024-12,1.0
|
||||
AcmeSaaS Inc.,EMP1011,Operations,Finance Analyst,2022-12-24,2024-01-01,Terminated,95000,95000.0,2024,12,2024-12,1.0
|
||||
AcmeSaaS Inc.,EMP1012,Operations,Operations Manager,2022-03-14,,Active,115000,112700.0,2024,12,2024-12,1.0
|
||||
AcmeSaaS Inc.,EMP1013,Sales,Account Executive,2023-01-01,,Active,90000,91800.0,2024,12,2024-12,0.5
|
||||
AcmeSaaS Inc.,EMP1014,Operations,Customer Success,2023-04-01,,Active,75000,75750.0,2024,12,2024-12,1.0
|
||||
AcmeSaaS Inc.,EMP1015,Engineering,Software Engineer,2023-07-01,,Active,120000,121200.0,2024,12,2024-12,0.5
|
||||
AcmeSaaS Inc.,EMP1016,Engineering,DevOps Engineer,2023-12-01,,Active,130000,130000.0,2024,12,2024-12,0.5
|
||||
AcmeSaaS Inc.,EMP1017,Operations,Finance Analyst,2024-03-01,,Active,95000,80750.0,2024,12,2024-12,1.0
|
||||
AcmeSaaS Inc.,EMP1018,Sales,Account Executive,2024-05-01,,Active,90000,58800.0,2024,12,2024-12,1.0
|
||||
AcmeSaaS Inc.,EMP1019,Sales,Account Executive,2024-06-01,,Active,90000,51975.0,2024,12,2024-12,0.5
|
||||
AcmeSaaS Inc.,EMP1020,Marketing,Marketing Manager,2024-08-01,,Active,110000,46291.67,2024,12,2024-12,1.0
|
||||
AcmeSaaS Inc.,EMP1021,Sales,Sales Manager,2024-11-01,,Active,140000,23100.0,2024,12,2024-12,1.0
|
||||
AcmeSaaS Inc.,EMP1022,Operations,Finance Analyst,2024-12-01,,Active,95000,7916.67,2024,12,2024-12,0.5
|
||||
|
@@ -1,673 +0,0 @@
|
||||
company,department,year,month,period,category,budget_amount,actual_amount,variance,variance_pct
|
||||
AcmeSaaS Inc.,Engineering,2023,1,2023-01,Salaries,16575.56,17009.7,434.14,2.62
|
||||
AcmeSaaS Inc.,Engineering,2023,1,2023-01,Software & Tools,10980.09,10113.47,-866.62,-7.89
|
||||
AcmeSaaS Inc.,Engineering,2023,1,2023-01,Travel,11090.59,11700.47,609.88,5.5
|
||||
AcmeSaaS Inc.,Engineering,2023,1,2023-01,Marketing Spend,7771.27,7143.48,-627.79,-8.08
|
||||
AcmeSaaS Inc.,Engineering,2023,1,2023-01,Cloud Infrastructure,8958.55,8699.37,-259.18,-2.89
|
||||
AcmeSaaS Inc.,Engineering,2023,1,2023-01,Contractors,22793.22,25471.1,2677.88,11.75
|
||||
AcmeSaaS Inc.,Engineering,2023,1,2023-01,Office & Facilities,16830.71,17396.22,565.51,3.36
|
||||
AcmeSaaS Inc.,Sales,2023,1,2023-01,Salaries,11002.74,10389.42,-613.32,-5.57
|
||||
AcmeSaaS Inc.,Sales,2023,1,2023-01,Software & Tools,12943.91,12046.07,-897.84,-6.94
|
||||
AcmeSaaS Inc.,Sales,2023,1,2023-01,Travel,15349.95,16981.62,1631.67,10.63
|
||||
AcmeSaaS Inc.,Sales,2023,1,2023-01,Marketing Spend,14333.45,15628.17,1294.72,9.03
|
||||
AcmeSaaS Inc.,Sales,2023,1,2023-01,Cloud Infrastructure,6016.93,5749.31,-267.62,-4.45
|
||||
AcmeSaaS Inc.,Sales,2023,1,2023-01,Contractors,3022.29,3135.04,112.75,3.73
|
||||
AcmeSaaS Inc.,Sales,2023,1,2023-01,Office & Facilities,7330.73,7147.11,-183.62,-2.5
|
||||
AcmeSaaS Inc.,Marketing,2023,1,2023-01,Salaries,13332.55,14605.51,1272.96,9.55
|
||||
AcmeSaaS Inc.,Marketing,2023,1,2023-01,Software & Tools,7713.33,7527.1,-186.23,-2.41
|
||||
AcmeSaaS Inc.,Marketing,2023,1,2023-01,Travel,5321.44,4962.97,-358.47,-6.74
|
||||
AcmeSaaS Inc.,Marketing,2023,1,2023-01,Marketing Spend,5096.37,5704.92,608.55,11.94
|
||||
AcmeSaaS Inc.,Marketing,2023,1,2023-01,Cloud Infrastructure,8977.47,8998.0,20.53,0.23
|
||||
AcmeSaaS Inc.,Marketing,2023,1,2023-01,Contractors,5295.07,4775.19,-519.88,-9.82
|
||||
AcmeSaaS Inc.,Marketing,2023,1,2023-01,Office & Facilities,9263.77,8256.87,-1006.9,-10.87
|
||||
AcmeSaaS Inc.,Operations,2023,1,2023-01,Salaries,2424.21,2441.15,16.94,0.7
|
||||
AcmeSaaS Inc.,Operations,2023,1,2023-01,Software & Tools,6967.02,7754.7,787.68,11.31
|
||||
AcmeSaaS Inc.,Operations,2023,1,2023-01,Travel,8411.4,9139.72,728.32,8.66
|
||||
AcmeSaaS Inc.,Operations,2023,1,2023-01,Marketing Spend,5165.97,4560.29,-605.68,-11.72
|
||||
AcmeSaaS Inc.,Operations,2023,1,2023-01,Cloud Infrastructure,2019.57,2126.55,106.98,5.3
|
||||
AcmeSaaS Inc.,Operations,2023,1,2023-01,Contractors,4810.3,5020.08,209.78,4.36
|
||||
AcmeSaaS Inc.,Operations,2023,1,2023-01,Office & Facilities,10201.53,10292.05,90.52,0.89
|
||||
AcmeSaaS Inc.,Engineering,2023,2,2023-02,Salaries,8498.07,8015.49,-482.58,-5.68
|
||||
AcmeSaaS Inc.,Engineering,2023,2,2023-02,Software & Tools,15832.55,15834.78,2.23,0.01
|
||||
AcmeSaaS Inc.,Engineering,2023,2,2023-02,Travel,5454.13,5033.49,-420.64,-7.71
|
||||
AcmeSaaS Inc.,Engineering,2023,2,2023-02,Marketing Spend,11790.32,12957.92,1167.6,9.9
|
||||
AcmeSaaS Inc.,Engineering,2023,2,2023-02,Cloud Infrastructure,12161.98,13243.48,1081.5,8.89
|
||||
AcmeSaaS Inc.,Engineering,2023,2,2023-02,Contractors,21965.66,20903.11,-1062.55,-4.84
|
||||
AcmeSaaS Inc.,Engineering,2023,2,2023-02,Office & Facilities,20437.29,21118.83,681.54,3.33
|
||||
AcmeSaaS Inc.,Sales,2023,2,2023-02,Salaries,12174.64,11660.84,-513.8,-4.22
|
||||
AcmeSaaS Inc.,Sales,2023,2,2023-02,Software & Tools,5015.07,4436.7,-578.37,-11.53
|
||||
AcmeSaaS Inc.,Sales,2023,2,2023-02,Travel,14584.67,16086.65,1501.98,10.3
|
||||
AcmeSaaS Inc.,Sales,2023,2,2023-02,Marketing Spend,11082.32,12089.63,1007.31,9.09
|
||||
AcmeSaaS Inc.,Sales,2023,2,2023-02,Cloud Infrastructure,14837.62,16018.69,1181.07,7.96
|
||||
AcmeSaaS Inc.,Sales,2023,2,2023-02,Contractors,10940.65,10435.23,-505.42,-4.62
|
||||
AcmeSaaS Inc.,Sales,2023,2,2023-02,Office & Facilities,2625.03,2346.52,-278.51,-10.61
|
||||
AcmeSaaS Inc.,Marketing,2023,2,2023-02,Salaries,11304.51,10296.31,-1008.2,-8.92
|
||||
AcmeSaaS Inc.,Marketing,2023,2,2023-02,Software & Tools,12050.51,11979.02,-71.49,-0.59
|
||||
AcmeSaaS Inc.,Marketing,2023,2,2023-02,Travel,2730.37,2763.01,32.64,1.2
|
||||
AcmeSaaS Inc.,Marketing,2023,2,2023-02,Marketing Spend,7062.45,6664.22,-398.23,-5.64
|
||||
AcmeSaaS Inc.,Marketing,2023,2,2023-02,Cloud Infrastructure,2552.46,2780.61,228.15,8.94
|
||||
AcmeSaaS Inc.,Marketing,2023,2,2023-02,Contractors,10034.04,9848.94,-185.1,-1.84
|
||||
AcmeSaaS Inc.,Marketing,2023,2,2023-02,Office & Facilities,10090.66,9392.7,-697.96,-6.92
|
||||
AcmeSaaS Inc.,Operations,2023,2,2023-02,Salaries,5656.81,5680.67,23.86,0.42
|
||||
AcmeSaaS Inc.,Operations,2023,2,2023-02,Software & Tools,7184.34,6530.86,-653.48,-9.1
|
||||
AcmeSaaS Inc.,Operations,2023,2,2023-02,Travel,2947.29,2752.55,-194.74,-6.61
|
||||
AcmeSaaS Inc.,Operations,2023,2,2023-02,Marketing Spend,3833.23,3684.27,-148.96,-3.89
|
||||
AcmeSaaS Inc.,Operations,2023,2,2023-02,Cloud Infrastructure,9309.51,9506.82,197.31,2.12
|
||||
AcmeSaaS Inc.,Operations,2023,2,2023-02,Contractors,6542.89,6119.09,-423.8,-6.48
|
||||
AcmeSaaS Inc.,Operations,2023,2,2023-02,Office & Facilities,4845.93,4520.54,-325.39,-6.71
|
||||
AcmeSaaS Inc.,Engineering,2023,3,2023-03,Salaries,5542.88,5767.67,224.79,4.06
|
||||
AcmeSaaS Inc.,Engineering,2023,3,2023-03,Software & Tools,18606.18,17330.11,-1276.07,-6.86
|
||||
AcmeSaaS Inc.,Engineering,2023,3,2023-03,Travel,9226.68,8412.47,-814.21,-8.82
|
||||
AcmeSaaS Inc.,Engineering,2023,3,2023-03,Marketing Spend,25004.01,27617.51,2613.5,10.45
|
||||
AcmeSaaS Inc.,Engineering,2023,3,2023-03,Cloud Infrastructure,23936.18,24344.3,408.12,1.71
|
||||
AcmeSaaS Inc.,Engineering,2023,3,2023-03,Contractors,5539.71,5503.38,-36.33,-0.66
|
||||
AcmeSaaS Inc.,Engineering,2023,3,2023-03,Office & Facilities,9438.05,10082.75,644.7,6.83
|
||||
AcmeSaaS Inc.,Sales,2023,3,2023-03,Salaries,16387.89,17069.75,681.86,4.16
|
||||
AcmeSaaS Inc.,Sales,2023,3,2023-03,Software & Tools,6006.93,6704.93,698.0,11.62
|
||||
AcmeSaaS Inc.,Sales,2023,3,2023-03,Travel,4434.38,4007.0,-427.38,-9.64
|
||||
AcmeSaaS Inc.,Sales,2023,3,2023-03,Marketing Spend,10055.12,9820.12,-235.0,-2.34
|
||||
AcmeSaaS Inc.,Sales,2023,3,2023-03,Cloud Infrastructure,9929.42,9546.47,-382.95,-3.86
|
||||
AcmeSaaS Inc.,Sales,2023,3,2023-03,Contractors,10660.29,11585.62,925.33,8.68
|
||||
AcmeSaaS Inc.,Sales,2023,3,2023-03,Office & Facilities,15068.65,14159.67,-908.98,-6.03
|
||||
AcmeSaaS Inc.,Marketing,2023,3,2023-03,Salaries,4905.59,5331.02,425.43,8.67
|
||||
AcmeSaaS Inc.,Marketing,2023,3,2023-03,Software & Tools,8457.6,8559.75,102.15,1.21
|
||||
AcmeSaaS Inc.,Marketing,2023,3,2023-03,Travel,8090.15,7217.56,-872.59,-10.79
|
||||
AcmeSaaS Inc.,Marketing,2023,3,2023-03,Marketing Spend,6119.85,6853.18,733.33,11.98
|
||||
AcmeSaaS Inc.,Marketing,2023,3,2023-03,Cloud Infrastructure,5724.81,6186.5,461.69,8.06
|
||||
AcmeSaaS Inc.,Marketing,2023,3,2023-03,Contractors,14982.14,16668.52,1686.38,11.26
|
||||
AcmeSaaS Inc.,Marketing,2023,3,2023-03,Office & Facilities,8382.24,9239.98,857.74,10.23
|
||||
AcmeSaaS Inc.,Operations,2023,3,2023-03,Salaries,11094.12,12386.3,1292.18,11.65
|
||||
AcmeSaaS Inc.,Operations,2023,3,2023-03,Software & Tools,3638.2,3433.18,-205.02,-5.64
|
||||
AcmeSaaS Inc.,Operations,2023,3,2023-03,Travel,7127.29,7613.21,485.92,6.82
|
||||
AcmeSaaS Inc.,Operations,2023,3,2023-03,Marketing Spend,4156.51,4111.63,-44.88,-1.08
|
||||
AcmeSaaS Inc.,Operations,2023,3,2023-03,Cloud Infrastructure,6202.92,6088.3,-114.62,-1.85
|
||||
AcmeSaaS Inc.,Operations,2023,3,2023-03,Contractors,2461.71,2731.9,270.19,10.98
|
||||
AcmeSaaS Inc.,Operations,2023,3,2023-03,Office & Facilities,5961.81,6670.68,708.87,11.89
|
||||
AcmeSaaS Inc.,Engineering,2023,4,2023-04,Salaries,14276.52,15126.17,849.65,5.95
|
||||
AcmeSaaS Inc.,Engineering,2023,4,2023-04,Software & Tools,17490.56,15631.66,-1858.9,-10.63
|
||||
AcmeSaaS Inc.,Engineering,2023,4,2023-04,Travel,6352.66,6481.0,128.34,2.02
|
||||
AcmeSaaS Inc.,Engineering,2023,4,2023-04,Marketing Spend,9157.05,9163.31,6.26,0.07
|
||||
AcmeSaaS Inc.,Engineering,2023,4,2023-04,Cloud Infrastructure,22436.93,24336.28,1899.35,8.47
|
||||
AcmeSaaS Inc.,Engineering,2023,4,2023-04,Contractors,14739.18,13527.38,-1211.8,-8.22
|
||||
AcmeSaaS Inc.,Engineering,2023,4,2023-04,Office & Facilities,14008.3,15557.43,1549.13,11.06
|
||||
AcmeSaaS Inc.,Sales,2023,4,2023-04,Salaries,4615.79,4334.65,-281.14,-6.09
|
||||
AcmeSaaS Inc.,Sales,2023,4,2023-04,Software & Tools,6593.07,6742.63,149.56,2.27
|
||||
AcmeSaaS Inc.,Sales,2023,4,2023-04,Travel,14247.02,14655.22,408.2,2.87
|
||||
AcmeSaaS Inc.,Sales,2023,4,2023-04,Marketing Spend,15746.68,15441.41,-305.27,-1.94
|
||||
AcmeSaaS Inc.,Sales,2023,4,2023-04,Cloud Infrastructure,7516.67,7667.61,150.94,2.01
|
||||
AcmeSaaS Inc.,Sales,2023,4,2023-04,Contractors,5359.75,5389.06,29.31,0.55
|
||||
AcmeSaaS Inc.,Sales,2023,4,2023-04,Office & Facilities,19769.48,21832.02,2062.54,10.43
|
||||
AcmeSaaS Inc.,Marketing,2023,4,2023-04,Salaries,5320.63,5642.25,321.62,6.04
|
||||
AcmeSaaS Inc.,Marketing,2023,4,2023-04,Software & Tools,12663.88,11364.7,-1299.18,-10.26
|
||||
AcmeSaaS Inc.,Marketing,2023,4,2023-04,Travel,5814.45,5756.24,-58.21,-1.0
|
||||
AcmeSaaS Inc.,Marketing,2023,4,2023-04,Marketing Spend,8067.92,9033.08,965.16,11.96
|
||||
AcmeSaaS Inc.,Marketing,2023,4,2023-04,Cloud Infrastructure,12025.54,13457.34,1431.8,11.91
|
||||
AcmeSaaS Inc.,Marketing,2023,4,2023-04,Contractors,6693.91,6008.34,-685.57,-10.24
|
||||
AcmeSaaS Inc.,Marketing,2023,4,2023-04,Office & Facilities,6926.0,6449.19,-476.81,-6.88
|
||||
AcmeSaaS Inc.,Operations,2023,4,2023-04,Salaries,3224.88,3382.41,157.53,4.88
|
||||
AcmeSaaS Inc.,Operations,2023,4,2023-04,Software & Tools,8213.48,8433.62,220.14,2.68
|
||||
AcmeSaaS Inc.,Operations,2023,4,2023-04,Travel,7822.23,8736.93,914.7,11.69
|
||||
AcmeSaaS Inc.,Operations,2023,4,2023-04,Marketing Spend,7810.33,8098.96,288.63,3.7
|
||||
AcmeSaaS Inc.,Operations,2023,4,2023-04,Cloud Infrastructure,4003.92,3530.97,-472.95,-11.81
|
||||
AcmeSaaS Inc.,Operations,2023,4,2023-04,Contractors,2422.49,2606.85,184.36,7.61
|
||||
AcmeSaaS Inc.,Operations,2023,4,2023-04,Office & Facilities,7470.37,7110.68,-359.69,-4.81
|
||||
AcmeSaaS Inc.,Engineering,2023,5,2023-05,Salaries,20932.03,21458.66,526.63,2.52
|
||||
AcmeSaaS Inc.,Engineering,2023,5,2023-05,Software & Tools,27880.52,29336.63,1456.11,5.22
|
||||
AcmeSaaS Inc.,Engineering,2023,5,2023-05,Travel,7589.44,7049.55,-539.89,-7.11
|
||||
AcmeSaaS Inc.,Engineering,2023,5,2023-05,Marketing Spend,7113.78,7342.97,229.19,3.22
|
||||
AcmeSaaS Inc.,Engineering,2023,5,2023-05,Cloud Infrastructure,6902.13,6511.17,-390.96,-5.66
|
||||
AcmeSaaS Inc.,Engineering,2023,5,2023-05,Contractors,18153.93,18103.96,-49.97,-0.28
|
||||
AcmeSaaS Inc.,Engineering,2023,5,2023-05,Office & Facilities,11070.91,12147.9,1076.99,9.73
|
||||
AcmeSaaS Inc.,Sales,2023,5,2023-05,Salaries,18054.52,17023.05,-1031.47,-5.71
|
||||
AcmeSaaS Inc.,Sales,2023,5,2023-05,Software & Tools,4616.54,4883.82,267.28,5.79
|
||||
AcmeSaaS Inc.,Sales,2023,5,2023-05,Travel,10522.17,10652.68,130.51,1.24
|
||||
AcmeSaaS Inc.,Sales,2023,5,2023-05,Marketing Spend,7903.48,7766.31,-137.17,-1.74
|
||||
AcmeSaaS Inc.,Sales,2023,5,2023-05,Cloud Infrastructure,3034.35,2677.27,-357.08,-11.77
|
||||
AcmeSaaS Inc.,Sales,2023,5,2023-05,Contractors,16717.78,15013.54,-1704.24,-10.19
|
||||
AcmeSaaS Inc.,Sales,2023,5,2023-05,Office & Facilities,14328.88,15646.36,1317.48,9.19
|
||||
AcmeSaaS Inc.,Marketing,2023,5,2023-05,Salaries,13535.8,14831.93,1296.13,9.58
|
||||
AcmeSaaS Inc.,Marketing,2023,5,2023-05,Software & Tools,9005.24,9645.24,640.0,7.11
|
||||
AcmeSaaS Inc.,Marketing,2023,5,2023-05,Travel,12659.2,13755.09,1095.89,8.66
|
||||
AcmeSaaS Inc.,Marketing,2023,5,2023-05,Marketing Spend,9472.02,10378.89,906.87,9.57
|
||||
AcmeSaaS Inc.,Marketing,2023,5,2023-05,Cloud Infrastructure,3979.6,3702.69,-276.91,-6.96
|
||||
AcmeSaaS Inc.,Marketing,2023,5,2023-05,Contractors,3718.53,3495.0,-223.53,-6.01
|
||||
AcmeSaaS Inc.,Marketing,2023,5,2023-05,Office & Facilities,6004.6,5432.18,-572.42,-9.53
|
||||
AcmeSaaS Inc.,Operations,2023,5,2023-05,Salaries,6732.88,7502.38,769.5,11.43
|
||||
AcmeSaaS Inc.,Operations,2023,5,2023-05,Software & Tools,7472.59,8029.93,557.34,7.46
|
||||
AcmeSaaS Inc.,Operations,2023,5,2023-05,Travel,4075.1,4448.13,373.03,9.15
|
||||
AcmeSaaS Inc.,Operations,2023,5,2023-05,Marketing Spend,5598.95,4960.38,-638.57,-11.41
|
||||
AcmeSaaS Inc.,Operations,2023,5,2023-05,Cloud Infrastructure,2284.3,2413.99,129.69,5.68
|
||||
AcmeSaaS Inc.,Operations,2023,5,2023-05,Contractors,7797.91,7483.85,-314.06,-4.03
|
||||
AcmeSaaS Inc.,Operations,2023,5,2023-05,Office & Facilities,7333.71,8091.98,758.27,10.34
|
||||
AcmeSaaS Inc.,Engineering,2023,6,2023-06,Salaries,17206.74,18687.59,1480.85,8.61
|
||||
AcmeSaaS Inc.,Engineering,2023,6,2023-06,Software & Tools,18304.76,17085.37,-1219.39,-6.66
|
||||
AcmeSaaS Inc.,Engineering,2023,6,2023-06,Travel,17357.94,18676.81,1318.87,7.6
|
||||
AcmeSaaS Inc.,Engineering,2023,6,2023-06,Marketing Spend,7698.04,7624.7,-73.34,-0.95
|
||||
AcmeSaaS Inc.,Engineering,2023,6,2023-06,Cloud Infrastructure,16942.83,16150.68,-792.15,-4.68
|
||||
AcmeSaaS Inc.,Engineering,2023,6,2023-06,Contractors,4879.51,5225.38,345.87,7.09
|
||||
AcmeSaaS Inc.,Engineering,2023,6,2023-06,Office & Facilities,18448.65,17242.53,-1206.12,-6.54
|
||||
AcmeSaaS Inc.,Sales,2023,6,2023-06,Salaries,3263.35,3184.78,-78.57,-2.41
|
||||
AcmeSaaS Inc.,Sales,2023,6,2023-06,Software & Tools,6168.95,6881.32,712.37,11.55
|
||||
AcmeSaaS Inc.,Sales,2023,6,2023-06,Travel,8485.88,8559.64,73.76,0.87
|
||||
AcmeSaaS Inc.,Sales,2023,6,2023-06,Marketing Spend,17677.52,19541.03,1863.51,10.54
|
||||
AcmeSaaS Inc.,Sales,2023,6,2023-06,Cloud Infrastructure,19435.57,17641.32,-1794.25,-9.23
|
||||
AcmeSaaS Inc.,Sales,2023,6,2023-06,Contractors,7643.4,8506.31,862.91,11.29
|
||||
AcmeSaaS Inc.,Sales,2023,6,2023-06,Office & Facilities,13856.24,12787.32,-1068.92,-7.71
|
||||
AcmeSaaS Inc.,Marketing,2023,6,2023-06,Salaries,14588.95,14628.95,40.0,0.27
|
||||
AcmeSaaS Inc.,Marketing,2023,6,2023-06,Software & Tools,5583.03,5429.2,-153.83,-2.76
|
||||
AcmeSaaS Inc.,Marketing,2023,6,2023-06,Travel,3553.81,3619.13,65.32,1.84
|
||||
AcmeSaaS Inc.,Marketing,2023,6,2023-06,Marketing Spend,7767.72,7310.46,-457.26,-5.89
|
||||
AcmeSaaS Inc.,Marketing,2023,6,2023-06,Cloud Infrastructure,11565.87,12145.42,579.55,5.01
|
||||
AcmeSaaS Inc.,Marketing,2023,6,2023-06,Contractors,6205.9,5463.71,-742.19,-11.96
|
||||
AcmeSaaS Inc.,Marketing,2023,6,2023-06,Office & Facilities,9985.33,11005.21,1019.88,10.21
|
||||
AcmeSaaS Inc.,Operations,2023,6,2023-06,Salaries,5946.87,5704.52,-242.35,-4.08
|
||||
AcmeSaaS Inc.,Operations,2023,6,2023-06,Software & Tools,7473.21,7139.45,-333.76,-4.47
|
||||
AcmeSaaS Inc.,Operations,2023,6,2023-06,Travel,7663.14,8303.19,640.05,8.35
|
||||
AcmeSaaS Inc.,Operations,2023,6,2023-06,Marketing Spend,7061.63,7434.07,372.44,5.27
|
||||
AcmeSaaS Inc.,Operations,2023,6,2023-06,Cloud Infrastructure,4477.43,4262.86,-214.57,-4.79
|
||||
AcmeSaaS Inc.,Operations,2023,6,2023-06,Contractors,1995.79,1904.44,-91.35,-4.58
|
||||
AcmeSaaS Inc.,Operations,2023,6,2023-06,Office & Facilities,7007.73,6853.66,-154.07,-2.2
|
||||
AcmeSaaS Inc.,Engineering,2023,7,2023-07,Salaries,11772.37,12098.74,326.37,2.77
|
||||
AcmeSaaS Inc.,Engineering,2023,7,2023-07,Software & Tools,9564.12,9107.22,-456.9,-4.78
|
||||
AcmeSaaS Inc.,Engineering,2023,7,2023-07,Travel,6081.08,6151.04,69.96,1.15
|
||||
AcmeSaaS Inc.,Engineering,2023,7,2023-07,Marketing Spend,12145.69,10689.39,-1456.3,-11.99
|
||||
AcmeSaaS Inc.,Engineering,2023,7,2023-07,Cloud Infrastructure,22901.3,21730.11,-1171.19,-5.11
|
||||
AcmeSaaS Inc.,Engineering,2023,7,2023-07,Contractors,17459.63,17165.84,-293.79,-1.68
|
||||
AcmeSaaS Inc.,Engineering,2023,7,2023-07,Office & Facilities,22124.33,22549.04,424.71,1.92
|
||||
AcmeSaaS Inc.,Sales,2023,7,2023-07,Salaries,12516.47,11524.24,-992.23,-7.93
|
||||
AcmeSaaS Inc.,Sales,2023,7,2023-07,Software & Tools,9625.46,8666.29,-959.17,-9.96
|
||||
AcmeSaaS Inc.,Sales,2023,7,2023-07,Travel,9277.59,9312.0,34.41,0.37
|
||||
AcmeSaaS Inc.,Sales,2023,7,2023-07,Marketing Spend,5796.23,5981.16,184.93,3.19
|
||||
AcmeSaaS Inc.,Sales,2023,7,2023-07,Cloud Infrastructure,9750.39,9364.72,-385.67,-3.96
|
||||
AcmeSaaS Inc.,Sales,2023,7,2023-07,Contractors,16272.38,17515.94,1243.56,7.64
|
||||
AcmeSaaS Inc.,Sales,2023,7,2023-07,Office & Facilities,14669.96,15554.16,884.2,6.03
|
||||
AcmeSaaS Inc.,Marketing,2023,7,2023-07,Salaries,13087.85,11746.07,-1341.78,-10.25
|
||||
AcmeSaaS Inc.,Marketing,2023,7,2023-07,Software & Tools,6100.78,5975.51,-125.27,-2.05
|
||||
AcmeSaaS Inc.,Marketing,2023,7,2023-07,Travel,5703.04,5880.65,177.61,3.11
|
||||
AcmeSaaS Inc.,Marketing,2023,7,2023-07,Marketing Spend,2979.27,2760.78,-218.49,-7.33
|
||||
AcmeSaaS Inc.,Marketing,2023,7,2023-07,Cloud Infrastructure,6415.74,6718.08,302.34,4.71
|
||||
AcmeSaaS Inc.,Marketing,2023,7,2023-07,Contractors,10006.19,9992.69,-13.5,-0.13
|
||||
AcmeSaaS Inc.,Marketing,2023,7,2023-07,Office & Facilities,15846.51,14872.84,-973.67,-6.14
|
||||
AcmeSaaS Inc.,Operations,2023,7,2023-07,Salaries,8509.09,9444.34,935.25,10.99
|
||||
AcmeSaaS Inc.,Operations,2023,7,2023-07,Software & Tools,1781.11,1788.79,7.68,0.43
|
||||
AcmeSaaS Inc.,Operations,2023,7,2023-07,Travel,9490.67,8466.18,-1024.49,-10.79
|
||||
AcmeSaaS Inc.,Operations,2023,7,2023-07,Marketing Spend,9688.03,9104.88,-583.15,-6.02
|
||||
AcmeSaaS Inc.,Operations,2023,7,2023-07,Cloud Infrastructure,2826.15,3062.42,236.27,8.36
|
||||
AcmeSaaS Inc.,Operations,2023,7,2023-07,Contractors,6120.87,6056.91,-63.96,-1.04
|
||||
AcmeSaaS Inc.,Operations,2023,7,2023-07,Office & Facilities,3542.89,3799.18,256.29,7.23
|
||||
AcmeSaaS Inc.,Engineering,2023,8,2023-08,Salaries,13071.65,13086.64,14.99,0.11
|
||||
AcmeSaaS Inc.,Engineering,2023,8,2023-08,Software & Tools,18090.61,19525.86,1435.25,7.93
|
||||
AcmeSaaS Inc.,Engineering,2023,8,2023-08,Travel,11941.53,12078.73,137.2,1.15
|
||||
AcmeSaaS Inc.,Engineering,2023,8,2023-08,Marketing Spend,17497.5,19165.54,1668.04,9.53
|
||||
AcmeSaaS Inc.,Engineering,2023,8,2023-08,Cloud Infrastructure,16579.09,17548.59,969.5,5.85
|
||||
AcmeSaaS Inc.,Engineering,2023,8,2023-08,Contractors,12211.03,12136.81,-74.22,-0.61
|
||||
AcmeSaaS Inc.,Engineering,2023,8,2023-08,Office & Facilities,13881.67,13079.39,-802.28,-5.78
|
||||
AcmeSaaS Inc.,Sales,2023,8,2023-08,Salaries,7603.28,7212.28,-391.0,-5.14
|
||||
AcmeSaaS Inc.,Sales,2023,8,2023-08,Software & Tools,14775.16,13965.65,-809.51,-5.48
|
||||
AcmeSaaS Inc.,Sales,2023,8,2023-08,Travel,17129.27,16388.09,-741.18,-4.33
|
||||
AcmeSaaS Inc.,Sales,2023,8,2023-08,Marketing Spend,12637.65,12759.43,121.78,0.96
|
||||
AcmeSaaS Inc.,Sales,2023,8,2023-08,Cloud Infrastructure,14574.7,13309.76,-1264.94,-8.68
|
||||
AcmeSaaS Inc.,Sales,2023,8,2023-08,Contractors,8105.83,7583.03,-522.8,-6.45
|
||||
AcmeSaaS Inc.,Sales,2023,8,2023-08,Office & Facilities,4484.93,4693.69,208.76,4.65
|
||||
AcmeSaaS Inc.,Marketing,2023,8,2023-08,Salaries,13559.97,14877.47,1317.5,9.72
|
||||
AcmeSaaS Inc.,Marketing,2023,8,2023-08,Software & Tools,3586.06,3658.42,72.36,2.02
|
||||
AcmeSaaS Inc.,Marketing,2023,8,2023-08,Travel,8918.97,9337.5,418.53,4.69
|
||||
AcmeSaaS Inc.,Marketing,2023,8,2023-08,Marketing Spend,11015.85,11958.98,943.13,8.56
|
||||
AcmeSaaS Inc.,Marketing,2023,8,2023-08,Cloud Infrastructure,9045.94,9622.55,576.61,6.37
|
||||
AcmeSaaS Inc.,Marketing,2023,8,2023-08,Contractors,5800.88,5634.35,-166.53,-2.87
|
||||
AcmeSaaS Inc.,Marketing,2023,8,2023-08,Office & Facilities,9113.8,8033.04,-1080.76,-11.86
|
||||
AcmeSaaS Inc.,Operations,2023,8,2023-08,Salaries,3786.02,3879.84,93.82,2.48
|
||||
AcmeSaaS Inc.,Operations,2023,8,2023-08,Software & Tools,6719.72,6269.02,-450.7,-6.71
|
||||
AcmeSaaS Inc.,Operations,2023,8,2023-08,Travel,7449.82,6948.16,-501.66,-6.73
|
||||
AcmeSaaS Inc.,Operations,2023,8,2023-08,Marketing Spend,8179.98,8054.01,-125.97,-1.54
|
||||
AcmeSaaS Inc.,Operations,2023,8,2023-08,Cloud Infrastructure,4277.23,3793.76,-483.47,-11.3
|
||||
AcmeSaaS Inc.,Operations,2023,8,2023-08,Contractors,6676.2,6413.63,-262.57,-3.93
|
||||
AcmeSaaS Inc.,Operations,2023,8,2023-08,Office & Facilities,5205.51,5429.32,223.81,4.3
|
||||
AcmeSaaS Inc.,Engineering,2023,9,2023-09,Salaries,17685.15,17958.46,273.31,1.55
|
||||
AcmeSaaS Inc.,Engineering,2023,9,2023-09,Software & Tools,10274.15,9108.08,-1166.07,-11.35
|
||||
AcmeSaaS Inc.,Engineering,2023,9,2023-09,Travel,19638.74,20311.56,672.82,3.43
|
||||
AcmeSaaS Inc.,Engineering,2023,9,2023-09,Marketing Spend,9115.23,8318.27,-796.96,-8.74
|
||||
AcmeSaaS Inc.,Engineering,2023,9,2023-09,Cloud Infrastructure,24435.45,24210.83,-224.62,-0.92
|
||||
AcmeSaaS Inc.,Engineering,2023,9,2023-09,Contractors,5997.43,5350.12,-647.31,-10.79
|
||||
AcmeSaaS Inc.,Engineering,2023,9,2023-09,Office & Facilities,17366.24,16862.36,-503.88,-2.9
|
||||
AcmeSaaS Inc.,Sales,2023,9,2023-09,Salaries,6524.4,5869.73,-654.67,-10.03
|
||||
AcmeSaaS Inc.,Sales,2023,9,2023-09,Software & Tools,8510.82,7529.11,-981.71,-11.53
|
||||
AcmeSaaS Inc.,Sales,2023,9,2023-09,Travel,16001.95,16153.34,151.39,0.95
|
||||
AcmeSaaS Inc.,Sales,2023,9,2023-09,Marketing Spend,9412.42,10541.7,1129.28,12.0
|
||||
AcmeSaaS Inc.,Sales,2023,9,2023-09,Cloud Infrastructure,15842.95,15272.45,-570.5,-3.6
|
||||
AcmeSaaS Inc.,Sales,2023,9,2023-09,Contractors,17221.11,17841.67,620.56,3.6
|
||||
AcmeSaaS Inc.,Sales,2023,9,2023-09,Office & Facilities,7224.76,7712.4,487.64,6.75
|
||||
AcmeSaaS Inc.,Marketing,2023,9,2023-09,Salaries,12611.77,13124.69,512.92,4.07
|
||||
AcmeSaaS Inc.,Marketing,2023,9,2023-09,Software & Tools,14190.95,14408.82,217.87,1.54
|
||||
AcmeSaaS Inc.,Marketing,2023,9,2023-09,Travel,17201.71,16037.35,-1164.36,-6.77
|
||||
AcmeSaaS Inc.,Marketing,2023,9,2023-09,Marketing Spend,5640.43,5910.45,270.02,4.79
|
||||
AcmeSaaS Inc.,Marketing,2023,9,2023-09,Cloud Infrastructure,2882.37,3067.0,184.63,6.41
|
||||
AcmeSaaS Inc.,Marketing,2023,9,2023-09,Contractors,4916.5,4524.5,-392.0,-7.97
|
||||
AcmeSaaS Inc.,Marketing,2023,9,2023-09,Office & Facilities,4513.36,4629.53,116.17,2.57
|
||||
AcmeSaaS Inc.,Operations,2023,9,2023-09,Salaries,9155.38,9545.06,389.68,4.26
|
||||
AcmeSaaS Inc.,Operations,2023,9,2023-09,Software & Tools,2814.9,3124.43,309.53,11.0
|
||||
AcmeSaaS Inc.,Operations,2023,9,2023-09,Travel,9869.87,9625.07,-244.8,-2.48
|
||||
AcmeSaaS Inc.,Operations,2023,9,2023-09,Marketing Spend,11325.57,11910.01,584.44,5.16
|
||||
AcmeSaaS Inc.,Operations,2023,9,2023-09,Cloud Infrastructure,2750.49,2470.6,-279.89,-10.18
|
||||
AcmeSaaS Inc.,Operations,2023,9,2023-09,Contractors,1925.44,2013.52,88.08,4.57
|
||||
AcmeSaaS Inc.,Operations,2023,9,2023-09,Office & Facilities,4791.19,4937.5,146.31,3.05
|
||||
AcmeSaaS Inc.,Engineering,2023,10,2023-10,Salaries,5280.53,5086.89,-193.64,-3.67
|
||||
AcmeSaaS Inc.,Engineering,2023,10,2023-10,Software & Tools,18465.34,18147.93,-317.41,-1.72
|
||||
AcmeSaaS Inc.,Engineering,2023,10,2023-10,Travel,19995.27,19374.16,-621.11,-3.11
|
||||
AcmeSaaS Inc.,Engineering,2023,10,2023-10,Marketing Spend,15082.14,15103.72,21.58,0.14
|
||||
AcmeSaaS Inc.,Engineering,2023,10,2023-10,Cloud Infrastructure,5657.13,5441.57,-215.56,-3.81
|
||||
AcmeSaaS Inc.,Engineering,2023,10,2023-10,Contractors,22621.12,24518.99,1897.87,8.39
|
||||
AcmeSaaS Inc.,Engineering,2023,10,2023-10,Office & Facilities,18664.99,20108.9,1443.91,7.74
|
||||
AcmeSaaS Inc.,Sales,2023,10,2023-10,Salaries,4013.91,4462.32,448.41,11.17
|
||||
AcmeSaaS Inc.,Sales,2023,10,2023-10,Software & Tools,16625.3,15707.91,-917.39,-5.52
|
||||
AcmeSaaS Inc.,Sales,2023,10,2023-10,Travel,11829.91,12704.94,875.03,7.4
|
||||
AcmeSaaS Inc.,Sales,2023,10,2023-10,Marketing Spend,14677.67,14812.14,134.47,0.92
|
||||
AcmeSaaS Inc.,Sales,2023,10,2023-10,Cloud Infrastructure,12887.53,12836.49,-51.04,-0.4
|
||||
AcmeSaaS Inc.,Sales,2023,10,2023-10,Contractors,8879.29,8742.0,-137.29,-1.55
|
||||
AcmeSaaS Inc.,Sales,2023,10,2023-10,Office & Facilities,13278.1,14014.32,736.22,5.54
|
||||
AcmeSaaS Inc.,Marketing,2023,10,2023-10,Salaries,5706.59,5857.7,151.11,2.65
|
||||
AcmeSaaS Inc.,Marketing,2023,10,2023-10,Software & Tools,13357.8,12969.86,-387.94,-2.9
|
||||
AcmeSaaS Inc.,Marketing,2023,10,2023-10,Travel,13082.59,11602.79,-1479.8,-11.31
|
||||
AcmeSaaS Inc.,Marketing,2023,10,2023-10,Marketing Spend,3322.85,3602.73,279.88,8.42
|
||||
AcmeSaaS Inc.,Marketing,2023,10,2023-10,Cloud Infrastructure,13750.23,12700.28,-1049.95,-7.64
|
||||
AcmeSaaS Inc.,Marketing,2023,10,2023-10,Contractors,5384.81,5012.77,-372.04,-6.91
|
||||
AcmeSaaS Inc.,Marketing,2023,10,2023-10,Office & Facilities,8281.57,8873.53,591.96,7.15
|
||||
AcmeSaaS Inc.,Operations,2023,10,2023-10,Salaries,4942.15,5203.18,261.03,5.28
|
||||
AcmeSaaS Inc.,Operations,2023,10,2023-10,Software & Tools,10205.75,10177.77,-27.98,-0.27
|
||||
AcmeSaaS Inc.,Operations,2023,10,2023-10,Travel,8459.57,8983.72,524.15,6.2
|
||||
AcmeSaaS Inc.,Operations,2023,10,2023-10,Marketing Spend,4317.61,4515.12,197.51,4.57
|
||||
AcmeSaaS Inc.,Operations,2023,10,2023-10,Cloud Infrastructure,1723.57,1783.92,60.35,3.5
|
||||
AcmeSaaS Inc.,Operations,2023,10,2023-10,Contractors,10866.08,10842.14,-23.94,-0.22
|
||||
AcmeSaaS Inc.,Operations,2023,10,2023-10,Office & Facilities,2459.15,2632.04,172.89,7.03
|
||||
AcmeSaaS Inc.,Engineering,2023,11,2023-11,Salaries,6838.62,6716.35,-122.27,-1.79
|
||||
AcmeSaaS Inc.,Engineering,2023,11,2023-11,Software & Tools,10223.25,10826.67,603.42,5.9
|
||||
AcmeSaaS Inc.,Engineering,2023,11,2023-11,Travel,22603.73,21685.79,-917.94,-4.06
|
||||
AcmeSaaS Inc.,Engineering,2023,11,2023-11,Marketing Spend,12451.09,13057.27,606.18,4.87
|
||||
AcmeSaaS Inc.,Engineering,2023,11,2023-11,Cloud Infrastructure,19701.25,18618.07,-1083.18,-5.5
|
||||
AcmeSaaS Inc.,Engineering,2023,11,2023-11,Contractors,16849.76,15844.45,-1005.31,-5.97
|
||||
AcmeSaaS Inc.,Engineering,2023,11,2023-11,Office & Facilities,18368.03,16695.76,-1672.27,-9.1
|
||||
AcmeSaaS Inc.,Sales,2023,11,2023-11,Salaries,8207.01,8649.37,442.36,5.39
|
||||
AcmeSaaS Inc.,Sales,2023,11,2023-11,Software & Tools,6538.67,7286.6,747.93,11.44
|
||||
AcmeSaaS Inc.,Sales,2023,11,2023-11,Travel,16049.17,16144.07,94.9,0.59
|
||||
AcmeSaaS Inc.,Sales,2023,11,2023-11,Marketing Spend,21219.53,20114.41,-1105.12,-5.21
|
||||
AcmeSaaS Inc.,Sales,2023,11,2023-11,Cloud Infrastructure,8037.18,7266.63,-770.55,-9.59
|
||||
AcmeSaaS Inc.,Sales,2023,11,2023-11,Contractors,8750.73,8108.32,-642.41,-7.34
|
||||
AcmeSaaS Inc.,Sales,2023,11,2023-11,Office & Facilities,14868.88,13896.4,-972.48,-6.54
|
||||
AcmeSaaS Inc.,Marketing,2023,11,2023-11,Salaries,5028.02,4577.04,-450.98,-8.97
|
||||
AcmeSaaS Inc.,Marketing,2023,11,2023-11,Software & Tools,2626.76,2859.05,232.29,8.84
|
||||
AcmeSaaS Inc.,Marketing,2023,11,2023-11,Travel,10180.77,10158.48,-22.29,-0.22
|
||||
AcmeSaaS Inc.,Marketing,2023,11,2023-11,Marketing Spend,6406.23,6979.28,573.05,8.95
|
||||
AcmeSaaS Inc.,Marketing,2023,11,2023-11,Cloud Infrastructure,16575.11,16869.74,294.63,1.78
|
||||
AcmeSaaS Inc.,Marketing,2023,11,2023-11,Contractors,10460.04,10383.21,-76.83,-0.73
|
||||
AcmeSaaS Inc.,Marketing,2023,11,2023-11,Office & Facilities,12552.82,12373.47,-179.35,-1.43
|
||||
AcmeSaaS Inc.,Operations,2023,11,2023-11,Salaries,3692.43,3807.14,114.71,3.11
|
||||
AcmeSaaS Inc.,Operations,2023,11,2023-11,Software & Tools,2293.56,2047.84,-245.72,-10.71
|
||||
AcmeSaaS Inc.,Operations,2023,11,2023-11,Travel,11652.04,10671.02,-981.02,-8.42
|
||||
AcmeSaaS Inc.,Operations,2023,11,2023-11,Marketing Spend,6778.3,6880.53,102.23,1.51
|
||||
AcmeSaaS Inc.,Operations,2023,11,2023-11,Cloud Infrastructure,10400.84,9911.17,-489.67,-4.71
|
||||
AcmeSaaS Inc.,Operations,2023,11,2023-11,Contractors,5968.12,6675.58,707.46,11.85
|
||||
AcmeSaaS Inc.,Operations,2023,11,2023-11,Office & Facilities,2532.4,2300.5,-231.9,-9.16
|
||||
AcmeSaaS Inc.,Engineering,2023,12,2023-12,Salaries,20294.73,22049.01,1754.28,8.64
|
||||
AcmeSaaS Inc.,Engineering,2023,12,2023-12,Software & Tools,16848.18,18829.65,1981.47,11.76
|
||||
AcmeSaaS Inc.,Engineering,2023,12,2023-12,Travel,20867.92,19893.21,-974.71,-4.67
|
||||
AcmeSaaS Inc.,Engineering,2023,12,2023-12,Marketing Spend,8551.85,8800.25,248.4,2.9
|
||||
AcmeSaaS Inc.,Engineering,2023,12,2023-12,Cloud Infrastructure,15022.85,15418.12,395.27,2.63
|
||||
AcmeSaaS Inc.,Engineering,2023,12,2023-12,Contractors,13452.25,14227.39,775.14,5.76
|
||||
AcmeSaaS Inc.,Engineering,2023,12,2023-12,Office & Facilities,13282.38,14709.2,1426.82,10.74
|
||||
AcmeSaaS Inc.,Sales,2023,12,2023-12,Salaries,12015.36,11872.63,-142.73,-1.19
|
||||
AcmeSaaS Inc.,Sales,2023,12,2023-12,Software & Tools,12119.24,12392.1,272.86,2.25
|
||||
AcmeSaaS Inc.,Sales,2023,12,2023-12,Travel,26539.52,25209.95,-1329.57,-5.01
|
||||
AcmeSaaS Inc.,Sales,2023,12,2023-12,Marketing Spend,10387.53,9718.1,-669.43,-6.44
|
||||
AcmeSaaS Inc.,Sales,2023,12,2023-12,Cloud Infrastructure,10925.21,11467.86,542.65,4.97
|
||||
AcmeSaaS Inc.,Sales,2023,12,2023-12,Contractors,7756.59,8134.47,377.88,4.87
|
||||
AcmeSaaS Inc.,Sales,2023,12,2023-12,Office & Facilities,5433.8,5373.85,-59.95,-1.1
|
||||
AcmeSaaS Inc.,Marketing,2023,12,2023-12,Salaries,8908.88,9004.16,95.28,1.07
|
||||
AcmeSaaS Inc.,Marketing,2023,12,2023-12,Software & Tools,11376.15,11779.23,403.08,3.54
|
||||
AcmeSaaS Inc.,Marketing,2023,12,2023-12,Travel,9956.63,10932.57,975.94,9.8
|
||||
AcmeSaaS Inc.,Marketing,2023,12,2023-12,Marketing Spend,8258.73,8906.14,647.41,7.84
|
||||
AcmeSaaS Inc.,Marketing,2023,12,2023-12,Cloud Infrastructure,8635.56,7747.29,-888.27,-10.29
|
||||
AcmeSaaS Inc.,Marketing,2023,12,2023-12,Contractors,11477.94,10557.66,-920.28,-8.02
|
||||
AcmeSaaS Inc.,Marketing,2023,12,2023-12,Office & Facilities,6173.31,5888.27,-285.04,-4.62
|
||||
AcmeSaaS Inc.,Operations,2023,12,2023-12,Salaries,7646.0,7646.87,0.87,0.01
|
||||
AcmeSaaS Inc.,Operations,2023,12,2023-12,Software & Tools,6144.98,6135.83,-9.15,-0.15
|
||||
AcmeSaaS Inc.,Operations,2023,12,2023-12,Travel,3801.83,3419.01,-382.82,-10.07
|
||||
AcmeSaaS Inc.,Operations,2023,12,2023-12,Marketing Spend,2430.19,2161.82,-268.37,-11.04
|
||||
AcmeSaaS Inc.,Operations,2023,12,2023-12,Cloud Infrastructure,7142.63,7026.11,-116.52,-1.63
|
||||
AcmeSaaS Inc.,Operations,2023,12,2023-12,Contractors,7234.95,6926.43,-308.52,-4.26
|
||||
AcmeSaaS Inc.,Operations,2023,12,2023-12,Office & Facilities,9263.66,8708.66,-555.0,-5.99
|
||||
AcmeSaaS Inc.,Engineering,2024,1,2024-01,Salaries,4522.32,4272.16,-250.16,-5.53
|
||||
AcmeSaaS Inc.,Engineering,2024,1,2024-01,Software & Tools,19782.63,17599.73,-2182.9,-11.03
|
||||
AcmeSaaS Inc.,Engineering,2024,1,2024-01,Travel,17574.83,18655.76,1080.93,6.15
|
||||
AcmeSaaS Inc.,Engineering,2024,1,2024-01,Marketing Spend,13004.03,12911.96,-92.07,-0.71
|
||||
AcmeSaaS Inc.,Engineering,2024,1,2024-01,Cloud Infrastructure,19587.62,20299.87,712.25,3.64
|
||||
AcmeSaaS Inc.,Engineering,2024,1,2024-01,Contractors,20442.79,22484.16,2041.37,9.99
|
||||
AcmeSaaS Inc.,Engineering,2024,1,2024-01,Office & Facilities,14705.77,13581.62,-1124.15,-7.64
|
||||
AcmeSaaS Inc.,Sales,2024,1,2024-01,Salaries,15089.94,16385.5,1295.56,8.59
|
||||
AcmeSaaS Inc.,Sales,2024,1,2024-01,Software & Tools,16082.33,15425.41,-656.92,-4.08
|
||||
AcmeSaaS Inc.,Sales,2024,1,2024-01,Travel,13211.64,13825.74,614.1,4.65
|
||||
AcmeSaaS Inc.,Sales,2024,1,2024-01,Marketing Spend,5175.33,4912.28,-263.05,-5.08
|
||||
AcmeSaaS Inc.,Sales,2024,1,2024-01,Cloud Infrastructure,10326.78,11430.16,1103.38,10.68
|
||||
AcmeSaaS Inc.,Sales,2024,1,2024-01,Contractors,10032.75,10787.77,755.02,7.53
|
||||
AcmeSaaS Inc.,Sales,2024,1,2024-01,Office & Facilities,16791.66,16993.55,201.89,1.2
|
||||
AcmeSaaS Inc.,Marketing,2024,1,2024-01,Salaries,7956.75,8257.82,301.07,3.78
|
||||
AcmeSaaS Inc.,Marketing,2024,1,2024-01,Software & Tools,6160.43,6223.4,62.97,1.02
|
||||
AcmeSaaS Inc.,Marketing,2024,1,2024-01,Travel,6272.54,6141.94,-130.6,-2.08
|
||||
AcmeSaaS Inc.,Marketing,2024,1,2024-01,Marketing Spend,14554.71,13463.4,-1091.31,-7.5
|
||||
AcmeSaaS Inc.,Marketing,2024,1,2024-01,Cloud Infrastructure,7308.29,7065.85,-242.44,-3.32
|
||||
AcmeSaaS Inc.,Marketing,2024,1,2024-01,Contractors,8721.97,9258.78,536.81,6.15
|
||||
AcmeSaaS Inc.,Marketing,2024,1,2024-01,Office & Facilities,14784.32,15229.3,444.98,3.01
|
||||
AcmeSaaS Inc.,Operations,2024,1,2024-01,Salaries,8383.62,9335.62,952.0,11.36
|
||||
AcmeSaaS Inc.,Operations,2024,1,2024-01,Software & Tools,3349.49,3437.01,87.52,2.61
|
||||
AcmeSaaS Inc.,Operations,2024,1,2024-01,Travel,6476.74,6071.5,-405.24,-6.26
|
||||
AcmeSaaS Inc.,Operations,2024,1,2024-01,Marketing Spend,9900.67,9088.92,-811.75,-8.2
|
||||
AcmeSaaS Inc.,Operations,2024,1,2024-01,Cloud Infrastructure,5471.57,5538.33,66.76,1.22
|
||||
AcmeSaaS Inc.,Operations,2024,1,2024-01,Contractors,7825.04,7923.17,98.13,1.25
|
||||
AcmeSaaS Inc.,Operations,2024,1,2024-01,Office & Facilities,2606.42,2351.96,-254.46,-9.76
|
||||
AcmeSaaS Inc.,Engineering,2024,2,2024-02,Salaries,22563.74,22611.78,48.04,0.21
|
||||
AcmeSaaS Inc.,Engineering,2024,2,2024-02,Software & Tools,21019.27,19876.28,-1142.99,-5.44
|
||||
AcmeSaaS Inc.,Engineering,2024,2,2024-02,Travel,12229.12,13211.53,982.41,8.03
|
||||
AcmeSaaS Inc.,Engineering,2024,2,2024-02,Marketing Spend,5531.94,6169.54,637.6,11.53
|
||||
AcmeSaaS Inc.,Engineering,2024,2,2024-02,Cloud Infrastructure,19446.39,18250.35,-1196.04,-6.15
|
||||
AcmeSaaS Inc.,Engineering,2024,2,2024-02,Contractors,12948.08,13107.39,159.31,1.23
|
||||
AcmeSaaS Inc.,Engineering,2024,2,2024-02,Office & Facilities,17196.88,16716.41,-480.47,-2.79
|
||||
AcmeSaaS Inc.,Sales,2024,2,2024-02,Salaries,16505.92,18226.16,1720.24,10.42
|
||||
AcmeSaaS Inc.,Sales,2024,2,2024-02,Software & Tools,10233.91,10252.91,19.0,0.19
|
||||
AcmeSaaS Inc.,Sales,2024,2,2024-02,Travel,15860.84,17081.04,1220.2,7.69
|
||||
AcmeSaaS Inc.,Sales,2024,2,2024-02,Marketing Spend,15628.85,14814.3,-814.55,-5.21
|
||||
AcmeSaaS Inc.,Sales,2024,2,2024-02,Cloud Infrastructure,6716.09,6391.39,-324.7,-4.83
|
||||
AcmeSaaS Inc.,Sales,2024,2,2024-02,Contractors,14506.44,14809.12,302.68,2.09
|
||||
AcmeSaaS Inc.,Sales,2024,2,2024-02,Office & Facilities,8819.19,9875.17,1055.98,11.97
|
||||
AcmeSaaS Inc.,Marketing,2024,2,2024-02,Salaries,10333.19,9891.2,-441.99,-4.28
|
||||
AcmeSaaS Inc.,Marketing,2024,2,2024-02,Software & Tools,4963.62,4592.72,-370.9,-7.47
|
||||
AcmeSaaS Inc.,Marketing,2024,2,2024-02,Travel,11103.72,11630.03,526.31,4.74
|
||||
AcmeSaaS Inc.,Marketing,2024,2,2024-02,Marketing Spend,8057.86,8196.71,138.85,1.72
|
||||
AcmeSaaS Inc.,Marketing,2024,2,2024-02,Cloud Infrastructure,11313.7,10590.25,-723.45,-6.39
|
||||
AcmeSaaS Inc.,Marketing,2024,2,2024-02,Contractors,11180.07,11919.42,739.35,6.61
|
||||
AcmeSaaS Inc.,Marketing,2024,2,2024-02,Office & Facilities,9793.22,8720.62,-1072.6,-10.95
|
||||
AcmeSaaS Inc.,Operations,2024,2,2024-02,Salaries,6439.15,6431.93,-7.22,-0.11
|
||||
AcmeSaaS Inc.,Operations,2024,2,2024-02,Software & Tools,6160.23,5475.73,-684.5,-11.11
|
||||
AcmeSaaS Inc.,Operations,2024,2,2024-02,Travel,6910.43,6914.23,3.8,0.05
|
||||
AcmeSaaS Inc.,Operations,2024,2,2024-02,Marketing Spend,3905.33,3989.85,84.52,2.16
|
||||
AcmeSaaS Inc.,Operations,2024,2,2024-02,Cloud Infrastructure,5866.74,6387.28,520.54,8.87
|
||||
AcmeSaaS Inc.,Operations,2024,2,2024-02,Contractors,6976.41,7602.93,626.52,8.98
|
||||
AcmeSaaS Inc.,Operations,2024,2,2024-02,Office & Facilities,8107.36,7991.21,-116.15,-1.43
|
||||
AcmeSaaS Inc.,Engineering,2024,3,2024-03,Salaries,17049.92,18969.89,1919.97,11.26
|
||||
AcmeSaaS Inc.,Engineering,2024,3,2024-03,Software & Tools,15350.8,14756.03,-594.77,-3.87
|
||||
AcmeSaaS Inc.,Engineering,2024,3,2024-03,Travel,21886.91,22899.16,1012.25,4.62
|
||||
AcmeSaaS Inc.,Engineering,2024,3,2024-03,Marketing Spend,14195.07,14705.54,510.47,3.6
|
||||
AcmeSaaS Inc.,Engineering,2024,3,2024-03,Cloud Infrastructure,20221.28,21928.43,1707.15,8.44
|
||||
AcmeSaaS Inc.,Engineering,2024,3,2024-03,Contractors,7902.63,8570.89,668.26,8.46
|
||||
AcmeSaaS Inc.,Engineering,2024,3,2024-03,Office & Facilities,15660.05,17010.61,1350.56,8.62
|
||||
AcmeSaaS Inc.,Sales,2024,3,2024-03,Salaries,11376.23,11734.34,358.11,3.15
|
||||
AcmeSaaS Inc.,Sales,2024,3,2024-03,Software & Tools,10057.96,11074.05,1016.09,10.1
|
||||
AcmeSaaS Inc.,Sales,2024,3,2024-03,Travel,18424.68,20624.26,2199.58,11.94
|
||||
AcmeSaaS Inc.,Sales,2024,3,2024-03,Marketing Spend,19271.32,20412.64,1141.32,5.92
|
||||
AcmeSaaS Inc.,Sales,2024,3,2024-03,Cloud Infrastructure,21622.43,21279.78,-342.65,-1.58
|
||||
AcmeSaaS Inc.,Sales,2024,3,2024-03,Contractors,4215.36,3809.11,-406.25,-9.64
|
||||
AcmeSaaS Inc.,Sales,2024,3,2024-03,Office & Facilities,4892.13,5049.16,157.03,3.21
|
||||
AcmeSaaS Inc.,Marketing,2024,3,2024-03,Salaries,13498.34,13092.87,-405.47,-3.0
|
||||
AcmeSaaS Inc.,Marketing,2024,3,2024-03,Software & Tools,7927.52,7253.18,-674.34,-8.51
|
||||
AcmeSaaS Inc.,Marketing,2024,3,2024-03,Travel,11178.86,11262.48,83.62,0.75
|
||||
AcmeSaaS Inc.,Marketing,2024,3,2024-03,Marketing Spend,13898.97,14118.89,219.92,1.58
|
||||
AcmeSaaS Inc.,Marketing,2024,3,2024-03,Cloud Infrastructure,2762.12,2956.03,193.91,7.02
|
||||
AcmeSaaS Inc.,Marketing,2024,3,2024-03,Contractors,12505.55,11515.06,-990.49,-7.92
|
||||
AcmeSaaS Inc.,Marketing,2024,3,2024-03,Office & Facilities,5975.2,5371.42,-603.78,-10.1
|
||||
AcmeSaaS Inc.,Operations,2024,3,2024-03,Salaries,9937.18,9353.65,-583.53,-5.87
|
||||
AcmeSaaS Inc.,Operations,2024,3,2024-03,Software & Tools,7531.88,6645.04,-886.84,-11.77
|
||||
AcmeSaaS Inc.,Operations,2024,3,2024-03,Travel,3902.98,4188.33,285.35,7.31
|
||||
AcmeSaaS Inc.,Operations,2024,3,2024-03,Marketing Spend,10339.35,11334.93,995.58,9.63
|
||||
AcmeSaaS Inc.,Operations,2024,3,2024-03,Cloud Infrastructure,2967.1,3093.58,126.48,4.26
|
||||
AcmeSaaS Inc.,Operations,2024,3,2024-03,Contractors,6013.19,5519.59,-493.6,-8.21
|
||||
AcmeSaaS Inc.,Operations,2024,3,2024-03,Office & Facilities,4028.91,3972.57,-56.34,-1.4
|
||||
AcmeSaaS Inc.,Engineering,2024,4,2024-04,Salaries,13055.4,12694.11,-361.29,-2.77
|
||||
AcmeSaaS Inc.,Engineering,2024,4,2024-04,Software & Tools,19223.48,19146.01,-77.47,-0.4
|
||||
AcmeSaaS Inc.,Engineering,2024,4,2024-04,Travel,20532.68,19237.67,-1295.01,-6.31
|
||||
AcmeSaaS Inc.,Engineering,2024,4,2024-04,Marketing Spend,15062.35,15322.35,260.0,1.73
|
||||
AcmeSaaS Inc.,Engineering,2024,4,2024-04,Cloud Infrastructure,10622.2,10812.92,190.72,1.8
|
||||
AcmeSaaS Inc.,Engineering,2024,4,2024-04,Contractors,25792.37,28842.22,3049.85,11.82
|
||||
AcmeSaaS Inc.,Engineering,2024,4,2024-04,Office & Facilities,9325.38,8867.09,-458.29,-4.91
|
||||
AcmeSaaS Inc.,Sales,2024,4,2024-04,Salaries,20439.41,20961.38,521.97,2.55
|
||||
AcmeSaaS Inc.,Sales,2024,4,2024-04,Software & Tools,14730.24,14718.67,-11.57,-0.08
|
||||
AcmeSaaS Inc.,Sales,2024,4,2024-04,Travel,7877.6,8641.71,764.11,9.7
|
||||
AcmeSaaS Inc.,Sales,2024,4,2024-04,Marketing Spend,13082.02,12410.74,-671.28,-5.13
|
||||
AcmeSaaS Inc.,Sales,2024,4,2024-04,Cloud Infrastructure,15222.56,16314.42,1091.86,7.17
|
||||
AcmeSaaS Inc.,Sales,2024,4,2024-04,Contractors,16273.79,16691.95,418.16,2.57
|
||||
AcmeSaaS Inc.,Sales,2024,4,2024-04,Office & Facilities,3851.97,3715.44,-136.53,-3.54
|
||||
AcmeSaaS Inc.,Marketing,2024,4,2024-04,Salaries,9285.5,10184.49,898.99,9.68
|
||||
AcmeSaaS Inc.,Marketing,2024,4,2024-04,Software & Tools,9103.71,9423.45,319.74,3.51
|
||||
AcmeSaaS Inc.,Marketing,2024,4,2024-04,Travel,9761.13,9313.52,-447.61,-4.59
|
||||
AcmeSaaS Inc.,Marketing,2024,4,2024-04,Marketing Spend,10260.08,10114.36,-145.72,-1.42
|
||||
AcmeSaaS Inc.,Marketing,2024,4,2024-04,Cloud Infrastructure,9546.32,9728.63,182.31,1.91
|
||||
AcmeSaaS Inc.,Marketing,2024,4,2024-04,Contractors,11617.26,12265.11,647.85,5.58
|
||||
AcmeSaaS Inc.,Marketing,2024,4,2024-04,Office & Facilities,9188.75,8284.87,-903.88,-9.84
|
||||
AcmeSaaS Inc.,Operations,2024,4,2024-04,Salaries,4566.14,5019.27,453.13,9.92
|
||||
AcmeSaaS Inc.,Operations,2024,4,2024-04,Software & Tools,9039.26,9756.19,716.93,7.93
|
||||
AcmeSaaS Inc.,Operations,2024,4,2024-04,Travel,3384.79,3187.36,-197.43,-5.83
|
||||
AcmeSaaS Inc.,Operations,2024,4,2024-04,Marketing Spend,2954.85,3185.11,230.26,7.79
|
||||
AcmeSaaS Inc.,Operations,2024,4,2024-04,Cloud Infrastructure,6981.8,6951.38,-30.42,-0.44
|
||||
AcmeSaaS Inc.,Operations,2024,4,2024-04,Contractors,11254.3,12082.14,827.84,7.36
|
||||
AcmeSaaS Inc.,Operations,2024,4,2024-04,Office & Facilities,6897.2,7305.34,408.14,5.92
|
||||
AcmeSaaS Inc.,Engineering,2024,5,2024-05,Salaries,11015.5,12284.33,1268.83,11.52
|
||||
AcmeSaaS Inc.,Engineering,2024,5,2024-05,Software & Tools,6143.01,6831.92,688.91,11.21
|
||||
AcmeSaaS Inc.,Engineering,2024,5,2024-05,Travel,24620.32,26420.09,1799.77,7.31
|
||||
AcmeSaaS Inc.,Engineering,2024,5,2024-05,Marketing Spend,6700.72,6484.86,-215.86,-3.22
|
||||
AcmeSaaS Inc.,Engineering,2024,5,2024-05,Cloud Infrastructure,24698.94,26422.03,1723.09,6.98
|
||||
AcmeSaaS Inc.,Engineering,2024,5,2024-05,Contractors,22380.69,19769.77,-2610.92,-11.67
|
||||
AcmeSaaS Inc.,Engineering,2024,5,2024-05,Office & Facilities,19418.04,19588.48,170.44,0.88
|
||||
AcmeSaaS Inc.,Sales,2024,5,2024-05,Salaries,10673.14,9991.31,-681.83,-6.39
|
||||
AcmeSaaS Inc.,Sales,2024,5,2024-05,Software & Tools,14417.9,12774.34,-1643.56,-11.4
|
||||
AcmeSaaS Inc.,Sales,2024,5,2024-05,Travel,14409.53,15738.32,1328.79,9.22
|
||||
AcmeSaaS Inc.,Sales,2024,5,2024-05,Marketing Spend,12901.94,13092.09,190.15,1.47
|
||||
AcmeSaaS Inc.,Sales,2024,5,2024-05,Cloud Infrastructure,16987.02,18679.97,1692.95,9.97
|
||||
AcmeSaaS Inc.,Sales,2024,5,2024-05,Contractors,19011.45,17740.12,-1271.33,-6.69
|
||||
AcmeSaaS Inc.,Sales,2024,5,2024-05,Office & Facilities,4723.21,4228.09,-495.12,-10.48
|
||||
AcmeSaaS Inc.,Marketing,2024,5,2024-05,Salaries,13824.33,13799.86,-24.47,-0.18
|
||||
AcmeSaaS Inc.,Marketing,2024,5,2024-05,Software & Tools,15018.07,13566.21,-1451.86,-9.67
|
||||
AcmeSaaS Inc.,Marketing,2024,5,2024-05,Travel,6543.65,7151.83,608.18,9.29
|
||||
AcmeSaaS Inc.,Marketing,2024,5,2024-05,Marketing Spend,8024.53,7322.86,-701.67,-8.74
|
||||
AcmeSaaS Inc.,Marketing,2024,5,2024-05,Cloud Infrastructure,4276.92,4229.34,-47.58,-1.11
|
||||
AcmeSaaS Inc.,Marketing,2024,5,2024-05,Contractors,15532.71,16168.26,635.55,4.09
|
||||
AcmeSaaS Inc.,Marketing,2024,5,2024-05,Office & Facilities,6574.0,6957.62,383.62,5.84
|
||||
AcmeSaaS Inc.,Operations,2024,5,2024-05,Salaries,11407.8,11156.23,-251.57,-2.21
|
||||
AcmeSaaS Inc.,Operations,2024,5,2024-05,Software & Tools,6006.09,6656.94,650.85,10.84
|
||||
AcmeSaaS Inc.,Operations,2024,5,2024-05,Travel,9319.24,8274.11,-1045.13,-11.21
|
||||
AcmeSaaS Inc.,Operations,2024,5,2024-05,Marketing Spend,3293.13,3190.8,-102.33,-3.11
|
||||
AcmeSaaS Inc.,Operations,2024,5,2024-05,Cloud Infrastructure,5962.59,5881.57,-81.02,-1.36
|
||||
AcmeSaaS Inc.,Operations,2024,5,2024-05,Contractors,2724.08,3018.64,294.56,10.81
|
||||
AcmeSaaS Inc.,Operations,2024,5,2024-05,Office & Facilities,6726.05,7299.84,573.79,8.53
|
||||
AcmeSaaS Inc.,Engineering,2024,6,2024-06,Salaries,7002.04,6367.08,-634.96,-9.07
|
||||
AcmeSaaS Inc.,Engineering,2024,6,2024-06,Software & Tools,22434.91,24308.85,1873.94,8.35
|
||||
AcmeSaaS Inc.,Engineering,2024,6,2024-06,Travel,18717.96,18514.54,-203.42,-1.09
|
||||
AcmeSaaS Inc.,Engineering,2024,6,2024-06,Marketing Spend,30125.01,31301.83,1176.82,3.91
|
||||
AcmeSaaS Inc.,Engineering,2024,6,2024-06,Cloud Infrastructure,13827.66,14297.93,470.27,3.4
|
||||
AcmeSaaS Inc.,Engineering,2024,6,2024-06,Contractors,14866.46,15213.07,346.61,2.33
|
||||
AcmeSaaS Inc.,Engineering,2024,6,2024-06,Office & Facilities,9382.9,8305.05,-1077.85,-11.49
|
||||
AcmeSaaS Inc.,Sales,2024,6,2024-06,Salaries,23008.66,21440.12,-1568.54,-6.82
|
||||
AcmeSaaS Inc.,Sales,2024,6,2024-06,Software & Tools,9899.69,10778.06,878.37,8.87
|
||||
AcmeSaaS Inc.,Sales,2024,6,2024-06,Travel,7060.71,6770.19,-290.52,-4.11
|
||||
AcmeSaaS Inc.,Sales,2024,6,2024-06,Marketing Spend,17646.19,16153.55,-1492.64,-8.46
|
||||
AcmeSaaS Inc.,Sales,2024,6,2024-06,Cloud Infrastructure,5677.63,6223.41,545.78,9.61
|
||||
AcmeSaaS Inc.,Sales,2024,6,2024-06,Contractors,22486.52,19803.44,-2683.08,-11.93
|
||||
AcmeSaaS Inc.,Sales,2024,6,2024-06,Office & Facilities,9021.02,9796.99,775.97,8.6
|
||||
AcmeSaaS Inc.,Marketing,2024,6,2024-06,Salaries,8588.41,9186.13,597.72,6.96
|
||||
AcmeSaaS Inc.,Marketing,2024,6,2024-06,Software & Tools,8183.04,7668.36,-514.68,-6.29
|
||||
AcmeSaaS Inc.,Marketing,2024,6,2024-06,Travel,11511.38,11024.51,-486.87,-4.23
|
||||
AcmeSaaS Inc.,Marketing,2024,6,2024-06,Marketing Spend,9410.66,8674.93,-735.73,-7.82
|
||||
AcmeSaaS Inc.,Marketing,2024,6,2024-06,Cloud Infrastructure,22831.95,20379.25,-2452.7,-10.74
|
||||
AcmeSaaS Inc.,Marketing,2024,6,2024-06,Contractors,5308.45,5616.41,307.96,5.8
|
||||
AcmeSaaS Inc.,Marketing,2024,6,2024-06,Office & Facilities,5007.24,5038.59,31.35,0.63
|
||||
AcmeSaaS Inc.,Operations,2024,6,2024-06,Salaries,7977.46,7103.19,-874.27,-10.96
|
||||
AcmeSaaS Inc.,Operations,2024,6,2024-06,Software & Tools,5621.65,6003.78,382.13,6.8
|
||||
AcmeSaaS Inc.,Operations,2024,6,2024-06,Travel,8260.35,8987.88,727.53,8.81
|
||||
AcmeSaaS Inc.,Operations,2024,6,2024-06,Marketing Spend,5945.11,5975.72,30.61,0.51
|
||||
AcmeSaaS Inc.,Operations,2024,6,2024-06,Cloud Infrastructure,2410.91,2386.63,-24.28,-1.01
|
||||
AcmeSaaS Inc.,Operations,2024,6,2024-06,Contractors,5862.92,6515.85,652.93,11.14
|
||||
AcmeSaaS Inc.,Operations,2024,6,2024-06,Office & Facilities,9724.09,8699.15,-1024.94,-10.54
|
||||
AcmeSaaS Inc.,Engineering,2024,7,2024-07,Salaries,17731.82,18192.69,460.87,2.6
|
||||
AcmeSaaS Inc.,Engineering,2024,7,2024-07,Software & Tools,15607.11,13980.28,-1626.83,-10.42
|
||||
AcmeSaaS Inc.,Engineering,2024,7,2024-07,Travel,23419.96,22155.37,-1264.59,-5.4
|
||||
AcmeSaaS Inc.,Engineering,2024,7,2024-07,Marketing Spend,18041.8,18618.03,576.23,3.19
|
||||
AcmeSaaS Inc.,Engineering,2024,7,2024-07,Cloud Infrastructure,29560.9,29903.97,343.07,1.16
|
||||
AcmeSaaS Inc.,Engineering,2024,7,2024-07,Contractors,6595.58,6318.86,-276.72,-4.2
|
||||
AcmeSaaS Inc.,Engineering,2024,7,2024-07,Office & Facilities,6796.06,7602.82,806.76,11.87
|
||||
AcmeSaaS Inc.,Sales,2024,7,2024-07,Salaries,13295.08,14153.29,858.21,6.46
|
||||
AcmeSaaS Inc.,Sales,2024,7,2024-07,Software & Tools,11829.82,12456.82,627.0,5.3
|
||||
AcmeSaaS Inc.,Sales,2024,7,2024-07,Travel,14722.74,13715.79,-1006.95,-6.84
|
||||
AcmeSaaS Inc.,Sales,2024,7,2024-07,Marketing Spend,5069.3,5010.36,-58.94,-1.16
|
||||
AcmeSaaS Inc.,Sales,2024,7,2024-07,Cloud Infrastructure,16560.05,15480.97,-1079.08,-6.52
|
||||
AcmeSaaS Inc.,Sales,2024,7,2024-07,Contractors,19439.67,18688.2,-751.47,-3.87
|
||||
AcmeSaaS Inc.,Sales,2024,7,2024-07,Office & Facilities,15590.17,15416.18,-173.99,-1.12
|
||||
AcmeSaaS Inc.,Marketing,2024,7,2024-07,Salaries,9928.85,8897.36,-1031.49,-10.39
|
||||
AcmeSaaS Inc.,Marketing,2024,7,2024-07,Software & Tools,4460.44,4815.6,355.16,7.96
|
||||
AcmeSaaS Inc.,Marketing,2024,7,2024-07,Travel,10112.45,9125.22,-987.23,-9.76
|
||||
AcmeSaaS Inc.,Marketing,2024,7,2024-07,Marketing Spend,14173.99,12801.6,-1372.39,-9.68
|
||||
AcmeSaaS Inc.,Marketing,2024,7,2024-07,Cloud Infrastructure,9218.45,9746.77,528.32,5.73
|
||||
AcmeSaaS Inc.,Marketing,2024,7,2024-07,Contractors,5441.18,5848.31,407.13,7.48
|
||||
AcmeSaaS Inc.,Marketing,2024,7,2024-07,Office & Facilities,18568.39,18819.6,251.21,1.35
|
||||
AcmeSaaS Inc.,Operations,2024,7,2024-07,Salaries,7665.93,8343.15,677.22,8.83
|
||||
AcmeSaaS Inc.,Operations,2024,7,2024-07,Software & Tools,7412.7,7805.98,393.28,5.31
|
||||
AcmeSaaS Inc.,Operations,2024,7,2024-07,Travel,5051.84,5619.75,567.91,11.24
|
||||
AcmeSaaS Inc.,Operations,2024,7,2024-07,Marketing Spend,2940.62,3011.48,70.86,2.41
|
||||
AcmeSaaS Inc.,Operations,2024,7,2024-07,Cloud Infrastructure,5295.64,5107.09,-188.55,-3.56
|
||||
AcmeSaaS Inc.,Operations,2024,7,2024-07,Contractors,8468.78,8627.15,158.37,1.87
|
||||
AcmeSaaS Inc.,Operations,2024,7,2024-07,Office & Facilities,9333.4,8689.93,-643.47,-6.89
|
||||
AcmeSaaS Inc.,Engineering,2024,8,2024-08,Salaries,20852.56,22390.08,1537.52,7.37
|
||||
AcmeSaaS Inc.,Engineering,2024,8,2024-08,Software & Tools,9899.78,10719.85,820.07,8.28
|
||||
AcmeSaaS Inc.,Engineering,2024,8,2024-08,Travel,6961.42,7754.26,792.84,11.39
|
||||
AcmeSaaS Inc.,Engineering,2024,8,2024-08,Marketing Spend,25629.77,27588.46,1958.69,7.64
|
||||
AcmeSaaS Inc.,Engineering,2024,8,2024-08,Cloud Infrastructure,13529.24,13898.01,368.77,2.73
|
||||
AcmeSaaS Inc.,Engineering,2024,8,2024-08,Contractors,23533.69,24339.67,805.98,3.42
|
||||
AcmeSaaS Inc.,Engineering,2024,8,2024-08,Office & Facilities,18759.81,16626.84,-2132.97,-11.37
|
||||
AcmeSaaS Inc.,Sales,2024,8,2024-08,Salaries,22785.27,20084.43,-2700.84,-11.85
|
||||
AcmeSaaS Inc.,Sales,2024,8,2024-08,Software & Tools,20713.68,22552.37,1838.69,8.88
|
||||
AcmeSaaS Inc.,Sales,2024,8,2024-08,Travel,9027.06,9170.74,143.68,1.59
|
||||
AcmeSaaS Inc.,Sales,2024,8,2024-08,Marketing Spend,7217.31,7045.45,-171.86,-2.38
|
||||
AcmeSaaS Inc.,Sales,2024,8,2024-08,Cloud Infrastructure,18077.76,16523.98,-1553.78,-8.59
|
||||
AcmeSaaS Inc.,Sales,2024,8,2024-08,Contractors,9890.79,10206.91,316.12,3.2
|
||||
AcmeSaaS Inc.,Sales,2024,8,2024-08,Office & Facilities,10532.08,9345.72,-1186.36,-11.26
|
||||
AcmeSaaS Inc.,Marketing,2024,8,2024-08,Salaries,14003.56,14230.73,227.17,1.62
|
||||
AcmeSaaS Inc.,Marketing,2024,8,2024-08,Software & Tools,5857.45,5273.99,-583.46,-9.96
|
||||
AcmeSaaS Inc.,Marketing,2024,8,2024-08,Travel,8997.88,8031.74,-966.14,-10.74
|
||||
AcmeSaaS Inc.,Marketing,2024,8,2024-08,Marketing Spend,7786.86,7146.61,-640.25,-8.22
|
||||
AcmeSaaS Inc.,Marketing,2024,8,2024-08,Cloud Infrastructure,8234.18,8467.05,232.87,2.83
|
||||
AcmeSaaS Inc.,Marketing,2024,8,2024-08,Contractors,13627.44,14196.42,568.98,4.18
|
||||
AcmeSaaS Inc.,Marketing,2024,8,2024-08,Office & Facilities,14474.91,13683.2,-791.71,-5.47
|
||||
AcmeSaaS Inc.,Operations,2024,8,2024-08,Salaries,8909.54,8446.06,-463.48,-5.2
|
||||
AcmeSaaS Inc.,Operations,2024,8,2024-08,Software & Tools,7014.13,7314.59,300.46,4.28
|
||||
AcmeSaaS Inc.,Operations,2024,8,2024-08,Travel,6545.13,6524.13,-21.0,-0.32
|
||||
AcmeSaaS Inc.,Operations,2024,8,2024-08,Marketing Spend,4729.29,4918.99,189.7,4.01
|
||||
AcmeSaaS Inc.,Operations,2024,8,2024-08,Cloud Infrastructure,9909.57,8828.44,-1081.13,-10.91
|
||||
AcmeSaaS Inc.,Operations,2024,8,2024-08,Contractors,3015.89,2940.08,-75.81,-2.51
|
||||
AcmeSaaS Inc.,Operations,2024,8,2024-08,Office & Facilities,6414.7,6567.61,152.91,2.38
|
||||
AcmeSaaS Inc.,Engineering,2024,9,2024-09,Salaries,8704.38,9220.41,516.03,5.93
|
||||
AcmeSaaS Inc.,Engineering,2024,9,2024-09,Software & Tools,23368.57,21549.72,-1818.85,-7.78
|
||||
AcmeSaaS Inc.,Engineering,2024,9,2024-09,Travel,18866.18,18323.77,-542.41,-2.88
|
||||
AcmeSaaS Inc.,Engineering,2024,9,2024-09,Marketing Spend,15171.87,15913.49,741.62,4.89
|
||||
AcmeSaaS Inc.,Engineering,2024,9,2024-09,Cloud Infrastructure,21077.08,21078.41,1.33,0.01
|
||||
AcmeSaaS Inc.,Engineering,2024,9,2024-09,Contractors,24701.68,26677.94,1976.26,8.0
|
||||
AcmeSaaS Inc.,Engineering,2024,9,2024-09,Office & Facilities,8706.51,9346.33,639.82,7.35
|
||||
AcmeSaaS Inc.,Sales,2024,9,2024-09,Salaries,5281.88,5374.93,93.05,1.76
|
||||
AcmeSaaS Inc.,Sales,2024,9,2024-09,Software & Tools,22752.78,23896.78,1144.0,5.03
|
||||
AcmeSaaS Inc.,Sales,2024,9,2024-09,Travel,4623.18,4531.86,-91.32,-1.98
|
||||
AcmeSaaS Inc.,Sales,2024,9,2024-09,Marketing Spend,4101.93,3723.08,-378.85,-9.24
|
||||
AcmeSaaS Inc.,Sales,2024,9,2024-09,Cloud Infrastructure,24066.89,21299.33,-2767.56,-11.5
|
||||
AcmeSaaS Inc.,Sales,2024,9,2024-09,Contractors,22760.42,21803.22,-957.2,-4.21
|
||||
AcmeSaaS Inc.,Sales,2024,9,2024-09,Office & Facilities,16425.26,17613.09,1187.83,7.23
|
||||
AcmeSaaS Inc.,Marketing,2024,9,2024-09,Salaries,10966.0,11029.07,63.07,0.58
|
||||
AcmeSaaS Inc.,Marketing,2024,9,2024-09,Software & Tools,13954.86,13605.77,-349.09,-2.5
|
||||
AcmeSaaS Inc.,Marketing,2024,9,2024-09,Travel,15180.92,14489.67,-691.25,-4.55
|
||||
AcmeSaaS Inc.,Marketing,2024,9,2024-09,Marketing Spend,3560.31,3423.18,-137.13,-3.85
|
||||
AcmeSaaS Inc.,Marketing,2024,9,2024-09,Cloud Infrastructure,14128.95,13562.89,-566.06,-4.01
|
||||
AcmeSaaS Inc.,Marketing,2024,9,2024-09,Contractors,5728.75,5272.47,-456.28,-7.96
|
||||
AcmeSaaS Inc.,Marketing,2024,9,2024-09,Office & Facilities,10557.23,10583.79,26.56,0.25
|
||||
AcmeSaaS Inc.,Operations,2024,9,2024-09,Salaries,2435.13,2281.0,-154.13,-6.33
|
||||
AcmeSaaS Inc.,Operations,2024,9,2024-09,Software & Tools,5869.94,5371.86,-498.08,-8.49
|
||||
AcmeSaaS Inc.,Operations,2024,9,2024-09,Travel,9305.14,8629.08,-676.06,-7.27
|
||||
AcmeSaaS Inc.,Operations,2024,9,2024-09,Marketing Spend,4476.87,4586.89,110.02,2.46
|
||||
AcmeSaaS Inc.,Operations,2024,9,2024-09,Cloud Infrastructure,7756.2,8240.59,484.39,6.25
|
||||
AcmeSaaS Inc.,Operations,2024,9,2024-09,Contractors,8550.61,8869.74,319.13,3.73
|
||||
AcmeSaaS Inc.,Operations,2024,9,2024-09,Office & Facilities,8516.67,7856.76,-659.91,-7.75
|
||||
AcmeSaaS Inc.,Engineering,2024,10,2024-10,Salaries,19482.03,20114.64,632.61,3.25
|
||||
AcmeSaaS Inc.,Engineering,2024,10,2024-10,Software & Tools,13702.19,14111.68,409.49,2.99
|
||||
AcmeSaaS Inc.,Engineering,2024,10,2024-10,Travel,19100.43,20770.18,1669.75,8.74
|
||||
AcmeSaaS Inc.,Engineering,2024,10,2024-10,Marketing Spend,19213.06,19799.68,586.62,3.05
|
||||
AcmeSaaS Inc.,Engineering,2024,10,2024-10,Cloud Infrastructure,12764.66,11695.36,-1069.3,-8.38
|
||||
AcmeSaaS Inc.,Engineering,2024,10,2024-10,Contractors,22619.55,20275.91,-2343.64,-10.36
|
||||
AcmeSaaS Inc.,Engineering,2024,10,2024-10,Office & Facilities,15161.5,14951.21,-210.29,-1.39
|
||||
AcmeSaaS Inc.,Sales,2024,10,2024-10,Salaries,15286.7,16503.63,1216.93,7.96
|
||||
AcmeSaaS Inc.,Sales,2024,10,2024-10,Software & Tools,14370.23,12910.44,-1459.79,-10.16
|
||||
AcmeSaaS Inc.,Sales,2024,10,2024-10,Travel,7255.72,7890.02,634.3,8.74
|
||||
AcmeSaaS Inc.,Sales,2024,10,2024-10,Marketing Spend,21945.84,23817.17,1871.33,8.53
|
||||
AcmeSaaS Inc.,Sales,2024,10,2024-10,Cloud Infrastructure,15533.74,15962.5,428.76,2.76
|
||||
AcmeSaaS Inc.,Sales,2024,10,2024-10,Contractors,20141.24,20175.41,34.17,0.17
|
||||
AcmeSaaS Inc.,Sales,2024,10,2024-10,Office & Facilities,7279.1,7213.96,-65.14,-0.89
|
||||
AcmeSaaS Inc.,Marketing,2024,10,2024-10,Salaries,9608.88,9552.68,-56.2,-0.58
|
||||
AcmeSaaS Inc.,Marketing,2024,10,2024-10,Software & Tools,12774.18,11703.79,-1070.39,-8.38
|
||||
AcmeSaaS Inc.,Marketing,2024,10,2024-10,Travel,14161.02,12671.98,-1489.04,-10.52
|
||||
AcmeSaaS Inc.,Marketing,2024,10,2024-10,Marketing Spend,8215.06,7433.32,-781.74,-9.52
|
||||
AcmeSaaS Inc.,Marketing,2024,10,2024-10,Cloud Infrastructure,13014.05,14260.67,1246.62,9.58
|
||||
AcmeSaaS Inc.,Marketing,2024,10,2024-10,Contractors,10908.59,10498.7,-409.89,-3.76
|
||||
AcmeSaaS Inc.,Marketing,2024,10,2024-10,Office & Facilities,6506.39,6841.05,334.66,5.14
|
||||
AcmeSaaS Inc.,Operations,2024,10,2024-10,Salaries,8695.16,8429.82,-265.34,-3.05
|
||||
AcmeSaaS Inc.,Operations,2024,10,2024-10,Software & Tools,4394.44,4165.47,-228.97,-5.21
|
||||
AcmeSaaS Inc.,Operations,2024,10,2024-10,Travel,5368.41,5250.87,-117.54,-2.19
|
||||
AcmeSaaS Inc.,Operations,2024,10,2024-10,Marketing Spend,7829.93,7526.19,-303.74,-3.88
|
||||
AcmeSaaS Inc.,Operations,2024,10,2024-10,Cloud Infrastructure,7851.48,8035.93,184.45,2.35
|
||||
AcmeSaaS Inc.,Operations,2024,10,2024-10,Contractors,8930.91,9550.84,619.93,6.94
|
||||
AcmeSaaS Inc.,Operations,2024,10,2024-10,Office & Facilities,4215.51,4364.54,149.03,3.54
|
||||
AcmeSaaS Inc.,Engineering,2024,11,2024-11,Salaries,6277.04,6839.38,562.34,8.96
|
||||
AcmeSaaS Inc.,Engineering,2024,11,2024-11,Software & Tools,7048.76,6766.86,-281.9,-4.0
|
||||
AcmeSaaS Inc.,Engineering,2024,11,2024-11,Travel,22806.85,23259.74,452.89,1.99
|
||||
AcmeSaaS Inc.,Engineering,2024,11,2024-11,Marketing Spend,12166.96,11119.91,-1047.05,-8.61
|
||||
AcmeSaaS Inc.,Engineering,2024,11,2024-11,Cloud Infrastructure,24030.92,23164.77,-866.15,-3.6
|
||||
AcmeSaaS Inc.,Engineering,2024,11,2024-11,Contractors,22218.08,24712.0,2493.92,11.22
|
||||
AcmeSaaS Inc.,Engineering,2024,11,2024-11,Office & Facilities,28959.33,30338.81,1379.48,4.76
|
||||
AcmeSaaS Inc.,Sales,2024,11,2024-11,Salaries,10756.3,11195.46,439.16,4.08
|
||||
AcmeSaaS Inc.,Sales,2024,11,2024-11,Software & Tools,14666.66,15824.6,1157.94,7.9
|
||||
AcmeSaaS Inc.,Sales,2024,11,2024-11,Travel,21270.37,22489.29,1218.92,5.73
|
||||
AcmeSaaS Inc.,Sales,2024,11,2024-11,Marketing Spend,9170.15,9578.22,408.07,4.45
|
||||
AcmeSaaS Inc.,Sales,2024,11,2024-11,Cloud Infrastructure,10462.11,10528.38,66.27,0.63
|
||||
AcmeSaaS Inc.,Sales,2024,11,2024-11,Contractors,18452.59,19099.28,646.69,3.5
|
||||
AcmeSaaS Inc.,Sales,2024,11,2024-11,Office & Facilities,18867.01,18520.19,-346.82,-1.84
|
||||
AcmeSaaS Inc.,Marketing,2024,11,2024-11,Salaries,10221.06,9331.99,-889.07,-8.7
|
||||
AcmeSaaS Inc.,Marketing,2024,11,2024-11,Software & Tools,10235.94,9197.19,-1038.75,-10.15
|
||||
AcmeSaaS Inc.,Marketing,2024,11,2024-11,Travel,6709.6,7264.23,554.63,8.27
|
||||
AcmeSaaS Inc.,Marketing,2024,11,2024-11,Marketing Spend,7365.8,6660.7,-705.1,-9.57
|
||||
AcmeSaaS Inc.,Marketing,2024,11,2024-11,Cloud Infrastructure,21551.18,22952.22,1401.04,6.5
|
||||
AcmeSaaS Inc.,Marketing,2024,11,2024-11,Contractors,12627.78,13643.42,1015.64,8.04
|
||||
AcmeSaaS Inc.,Marketing,2024,11,2024-11,Office & Facilities,7604.65,8304.91,700.26,9.21
|
||||
AcmeSaaS Inc.,Operations,2024,11,2024-11,Salaries,2558.05,2724.49,166.44,6.51
|
||||
AcmeSaaS Inc.,Operations,2024,11,2024-11,Software & Tools,6299.96,6767.23,467.27,7.42
|
||||
AcmeSaaS Inc.,Operations,2024,11,2024-11,Travel,11675.29,10738.11,-937.18,-8.03
|
||||
AcmeSaaS Inc.,Operations,2024,11,2024-11,Marketing Spend,3725.63,3669.9,-55.73,-1.5
|
||||
AcmeSaaS Inc.,Operations,2024,11,2024-11,Cloud Infrastructure,6799.97,6654.49,-145.48,-2.14
|
||||
AcmeSaaS Inc.,Operations,2024,11,2024-11,Contractors,4116.05,4290.27,174.22,4.23
|
||||
AcmeSaaS Inc.,Operations,2024,11,2024-11,Office & Facilities,12489.18,11702.45,-786.73,-6.3
|
||||
AcmeSaaS Inc.,Engineering,2024,12,2024-12,Salaries,16089.84,15970.19,-119.65,-0.74
|
||||
AcmeSaaS Inc.,Engineering,2024,12,2024-12,Software & Tools,11894.74,12851.4,956.66,8.04
|
||||
AcmeSaaS Inc.,Engineering,2024,12,2024-12,Travel,24105.92,23341.32,-764.6,-3.17
|
||||
AcmeSaaS Inc.,Engineering,2024,12,2024-12,Marketing Spend,16214.4,17954.39,1739.99,10.73
|
||||
AcmeSaaS Inc.,Engineering,2024,12,2024-12,Cloud Infrastructure,18455.45,20601.18,2145.73,11.63
|
||||
AcmeSaaS Inc.,Engineering,2024,12,2024-12,Contractors,12541.11,12425.77,-115.34,-0.92
|
||||
AcmeSaaS Inc.,Engineering,2024,12,2024-12,Office & Facilities,25688.58,24343.15,-1345.43,-5.24
|
||||
AcmeSaaS Inc.,Sales,2024,12,2024-12,Salaries,11463.43,11851.85,388.42,3.39
|
||||
AcmeSaaS Inc.,Sales,2024,12,2024-12,Software & Tools,14505.93,15808.39,1302.46,8.98
|
||||
AcmeSaaS Inc.,Sales,2024,12,2024-12,Travel,23676.2,23986.12,309.92,1.31
|
||||
AcmeSaaS Inc.,Sales,2024,12,2024-12,Marketing Spend,20554.51,18594.05,-1960.46,-9.54
|
||||
AcmeSaaS Inc.,Sales,2024,12,2024-12,Cloud Infrastructure,20227.83,21907.03,1679.2,8.3
|
||||
AcmeSaaS Inc.,Sales,2024,12,2024-12,Contractors,6375.29,6912.6,537.31,8.43
|
||||
AcmeSaaS Inc.,Sales,2024,12,2024-12,Office & Facilities,8707.61,8258.43,-449.18,-5.16
|
||||
AcmeSaaS Inc.,Marketing,2024,12,2024-12,Salaries,14815.77,14641.99,-173.78,-1.17
|
||||
AcmeSaaS Inc.,Marketing,2024,12,2024-12,Software & Tools,7002.61,6749.82,-252.79,-3.61
|
||||
AcmeSaaS Inc.,Marketing,2024,12,2024-12,Travel,17081.5,15141.06,-1940.44,-11.36
|
||||
AcmeSaaS Inc.,Marketing,2024,12,2024-12,Marketing Spend,5003.72,4467.23,-536.49,-10.72
|
||||
AcmeSaaS Inc.,Marketing,2024,12,2024-12,Cloud Infrastructure,9626.74,9631.38,4.64,0.05
|
||||
AcmeSaaS Inc.,Marketing,2024,12,2024-12,Contractors,17736.53,16611.8,-1124.73,-6.34
|
||||
AcmeSaaS Inc.,Marketing,2024,12,2024-12,Office & Facilities,6193.87,6929.0,735.13,11.87
|
||||
AcmeSaaS Inc.,Operations,2024,12,2024-12,Salaries,5290.09,5019.51,-270.58,-5.11
|
||||
AcmeSaaS Inc.,Operations,2024,12,2024-12,Software & Tools,1903.32,2053.95,150.63,7.91
|
||||
AcmeSaaS Inc.,Operations,2024,12,2024-12,Travel,10720.19,11224.65,504.46,4.71
|
||||
AcmeSaaS Inc.,Operations,2024,12,2024-12,Marketing Spend,9824.97,8973.25,-851.72,-8.67
|
||||
AcmeSaaS Inc.,Operations,2024,12,2024-12,Cloud Infrastructure,7976.73,8370.21,393.48,4.93
|
||||
AcmeSaaS Inc.,Operations,2024,12,2024-12,Contractors,9358.11,9242.67,-115.44,-1.23
|
||||
AcmeSaaS Inc.,Operations,2024,12,2024-12,Office & Facilities,2972.04,2619.14,-352.9,-11.87
|
||||
|
@@ -1,25 +0,0 @@
|
||||
company,year,month,period,product_revenue,service_revenue,total_revenue,cogs_product,cogs_service,total_cogs,gross_profit,gross_margin_pct,total_opex,ebitda,ebitda_margin_pct,net_income
|
||||
AcmeSaaS Inc.,2023,1,2023-01,167881.7,71338.86,239220.56,43649.24,32102.49,75751.73,163468.83,68.33,258734.13,-95265.3,-39.82,-71448.98
|
||||
AcmeSaaS Inc.,2023,2,2023-02,178629.98,69239.49,247869.47,44657.5,32542.56,77200.06,170669.41,68.85,262477.89,-91808.48,-37.04,-68856.36
|
||||
AcmeSaaS Inc.,2023,3,2023-03,174480.8,77504.48,251985.28,41875.39,33326.93,75202.32,176782.96,70.16,263811.87,-87028.91,-34.54,-65271.68
|
||||
AcmeSaaS Inc.,2023,4,2023-04,198938.2,71926.16,270864.36,51723.93,30928.25,82652.18,188212.18,69.49,277471.78,-89259.6,-32.95,-66944.7
|
||||
AcmeSaaS Inc.,2023,5,2023-05,195203.87,79126.8,274330.67,48800.97,37980.86,86781.83,187548.84,68.37,264394.63,-76845.79,-28.01,-57634.34
|
||||
AcmeSaaS Inc.,2023,6,2023-06,211243.76,82868.84,294112.6,54923.38,38119.67,93043.05,201069.55,68.36,271048.89,-69979.34,-23.79,-52484.5
|
||||
AcmeSaaS Inc.,2023,7,2023-07,197893.06,86586.64,284479.7,49473.26,39829.85,89303.11,195176.59,68.61,286660.29,-91483.7,-32.16,-68612.77
|
||||
AcmeSaaS Inc.,2023,8,2023-08,212514.63,79799.67,292314.3,51003.51,34313.86,85317.37,206996.93,70.81,287657.57,-80660.64,-27.59,-60495.48
|
||||
AcmeSaaS Inc.,2023,9,2023-09,220822.47,80318.59,301141.06,57413.84,34536.99,91950.83,209190.23,69.47,294485.72,-85295.49,-28.32,-63971.62
|
||||
AcmeSaaS Inc.,2023,10,2023-10,211288.73,83490.66,294779.39,54935.07,38405.7,93340.77,201438.62,68.34,306228.63,-104790.01,-35.55,-78592.51
|
||||
AcmeSaaS Inc.,2023,11,2023-11,230323.91,94427.01,324750.92,57580.98,44380.69,101961.67,222789.25,68.6,299769.68,-76980.43,-23.7,-57735.32
|
||||
AcmeSaaS Inc.,2023,12,2023-12,237893.99,85187.11,323081.1,57094.56,39186.07,96280.63,226800.47,70.2,300204.79,-73404.32,-22.72,-55053.24
|
||||
AcmeSaaS Inc.,2024,1,2024-01,236329.99,81463.84,317793.83,61445.8,35844.09,97289.89,220503.94,69.39,323370.74,-102866.8,-32.37,-77150.1
|
||||
AcmeSaaS Inc.,2024,2,2024-02,231260.65,93519.61,324780.26,57815.16,43954.22,101769.38,223010.88,68.67,318477.89,-95467.01,-29.39,-71600.26
|
||||
AcmeSaaS Inc.,2024,3,2024-03,265782.9,83786.47,349569.37,63787.9,39379.64,103167.54,246401.83,70.49,311338.28,-64936.45,-18.58,-48702.34
|
||||
AcmeSaaS Inc.,2024,4,2024-04,268162.53,102184.41,370346.94,67040.63,47004.83,114045.46,256301.48,69.21,321097.34,-64795.86,-17.5,-48596.9
|
||||
AcmeSaaS Inc.,2024,5,2024-05,256505.97,87815.99,344321.96,66691.55,39517.2,106208.75,238113.21,69.15,316380.7,-78267.49,-22.73,-58700.62
|
||||
AcmeSaaS Inc.,2024,6,2024-06,271536.83,106191.45,377728.28,65168.84,49909.98,115078.82,262649.46,69.53,325000.96,-62351.5,-16.51,-46763.62
|
||||
AcmeSaaS Inc.,2024,7,2024-07,298646.5,96974.17,395620.67,71675.16,44608.12,116283.28,279337.39,70.61,330776.36,-51438.97,-13.0,-38579.23
|
||||
AcmeSaaS Inc.,2024,8,2024-08,289698.65,107310.47,397009.12,75321.65,49362.82,124684.47,272324.65,68.59,354189.36,-81864.71,-20.62,-61398.53
|
||||
AcmeSaaS Inc.,2024,9,2024-09,277082.24,94973.08,372055.32,66499.74,44637.35,111137.09,260918.23,70.13,332157.93,-71239.7,-19.15,-53429.77
|
||||
AcmeSaaS Inc.,2024,10,2024-10,304823.0,104824.94,409647.94,73157.52,46122.97,119280.49,290367.45,70.88,365913.14,-75545.69,-18.44,-56659.27
|
||||
AcmeSaaS Inc.,2024,11,2024-11,325914.43,95195.56,421109.99,84737.75,43789.96,128527.71,292582.28,69.48,354860.87,-62278.59,-14.79,-46708.94
|
||||
AcmeSaaS Inc.,2024,12,2024-12,331873.11,108581.53,440454.64,79649.55,52119.13,131768.68,308685.96,70.08,352490.46,-43804.5,-9.95,-32853.38
|
||||
|
@@ -1,49 +0,0 @@
|
||||
company,year,month,period,revenue_type,budget_amount,actual_amount,variance,variance_pct
|
||||
AcmeSaaS Inc.,2023,1,2023-01,Product,180000,185019.36,5019.36,2.79
|
||||
AcmeSaaS Inc.,2023,1,2023-01,Service,75000,67875.16,-7124.84,-9.5
|
||||
AcmeSaaS Inc.,2023,2,2023-02,Product,184500.0,176198.58,-8301.42,-4.5
|
||||
AcmeSaaS Inc.,2023,2,2023-02,Service,76125.0,71910.88,-4214.12,-5.54
|
||||
AcmeSaaS Inc.,2023,3,2023-03,Product,189112.5,198056.43,8943.93,4.73
|
||||
AcmeSaaS Inc.,2023,3,2023-03,Service,77266.87,79997.47,2730.6,3.53
|
||||
AcmeSaaS Inc.,2023,4,2023-04,Product,193840.31,209044.35,15204.04,7.84
|
||||
AcmeSaaS Inc.,2023,4,2023-04,Service,78425.88,71946.94,-6478.94,-8.26
|
||||
AcmeSaaS Inc.,2023,5,2023-05,Product,198686.32,195583.71,-3102.61,-1.56
|
||||
AcmeSaaS Inc.,2023,5,2023-05,Service,79602.27,72116.43,-7485.84,-9.4
|
||||
AcmeSaaS Inc.,2023,6,2023-06,Product,203653.48,192193.41,-11460.07,-5.63
|
||||
AcmeSaaS Inc.,2023,6,2023-06,Service,80796.3,80882.84,86.54,0.11
|
||||
AcmeSaaS Inc.,2023,7,2023-07,Product,208744.82,188978.19,-19766.63,-9.47
|
||||
AcmeSaaS Inc.,2023,7,2023-07,Service,82008.24,77068.68,-4939.56,-6.02
|
||||
AcmeSaaS Inc.,2023,8,2023-08,Product,213963.44,220377.4,6413.96,3.0
|
||||
AcmeSaaS Inc.,2023,8,2023-08,Service,83238.37,83986.54,748.17,0.9
|
||||
AcmeSaaS Inc.,2023,9,2023-09,Product,219312.52,207050.35,-12262.17,-5.59
|
||||
AcmeSaaS Inc.,2023,9,2023-09,Service,84486.94,85995.3,1508.36,1.79
|
||||
AcmeSaaS Inc.,2023,10,2023-10,Product,224795.33,238707.03,13911.7,6.19
|
||||
AcmeSaaS Inc.,2023,10,2023-10,Service,85754.25,77290.28,-8463.97,-9.87
|
||||
AcmeSaaS Inc.,2023,11,2023-11,Product,230415.22,244508.3,14093.08,6.12
|
||||
AcmeSaaS Inc.,2023,11,2023-11,Service,87040.56,90489.79,3449.23,3.96
|
||||
AcmeSaaS Inc.,2023,12,2023-12,Product,236175.6,228629.81,-7545.79,-3.19
|
||||
AcmeSaaS Inc.,2023,12,2023-12,Service,88346.17,82258.76,-6087.41,-6.89
|
||||
AcmeSaaS Inc.,2024,1,2024-01,Product,242079.99,264216.42,22136.43,9.14
|
||||
AcmeSaaS Inc.,2024,1,2024-01,Service,89671.36,86740.8,-2930.56,-3.27
|
||||
AcmeSaaS Inc.,2024,2,2024-02,Product,248131.99,227921.43,-20210.56,-8.15
|
||||
AcmeSaaS Inc.,2024,2,2024-02,Service,91016.43,83675.34,-7341.09,-8.07
|
||||
AcmeSaaS Inc.,2024,3,2024-03,Product,254335.29,272011.31,17676.02,6.95
|
||||
AcmeSaaS Inc.,2024,3,2024-03,Service,92381.68,94298.16,1916.48,2.07
|
||||
AcmeSaaS Inc.,2024,4,2024-04,Product,260693.67,276706.95,16013.28,6.14
|
||||
AcmeSaaS Inc.,2024,4,2024-04,Service,93767.4,98075.67,4308.27,4.59
|
||||
AcmeSaaS Inc.,2024,5,2024-05,Product,267211.01,269147.12,1936.11,0.72
|
||||
AcmeSaaS Inc.,2024,5,2024-05,Service,95173.92,104179.58,9005.66,9.46
|
||||
AcmeSaaS Inc.,2024,6,2024-06,Product,273891.29,267237.61,-6653.68,-2.43
|
||||
AcmeSaaS Inc.,2024,6,2024-06,Service,96601.52,97606.96,1005.44,1.04
|
||||
AcmeSaaS Inc.,2024,7,2024-07,Product,280738.57,299233.89,18495.32,6.59
|
||||
AcmeSaaS Inc.,2024,7,2024-07,Service,98050.55,100374.74,2324.19,2.37
|
||||
AcmeSaaS Inc.,2024,8,2024-08,Product,287757.03,308573.77,20816.74,7.23
|
||||
AcmeSaaS Inc.,2024,8,2024-08,Service,99521.31,101060.95,1539.64,1.55
|
||||
AcmeSaaS Inc.,2024,9,2024-09,Product,294950.96,307018.69,12067.73,4.09
|
||||
AcmeSaaS Inc.,2024,9,2024-09,Service,101014.13,91838.5,-9175.63,-9.08
|
||||
AcmeSaaS Inc.,2024,10,2024-10,Product,302324.73,285872.11,-16452.62,-5.44
|
||||
AcmeSaaS Inc.,2024,10,2024-10,Service,102529.34,98210.56,-4318.78,-4.21
|
||||
AcmeSaaS Inc.,2024,11,2024-11,Product,309882.85,283839.8,-26043.05,-8.4
|
||||
AcmeSaaS Inc.,2024,11,2024-11,Service,104067.28,98505.73,-5561.55,-5.34
|
||||
AcmeSaaS Inc.,2024,12,2024-12,Product,317629.92,292283.14,-25346.78,-7.98
|
||||
AcmeSaaS Inc.,2024,12,2024-12,Service,105628.29,100937.84,-4690.45,-4.44
|
||||
|
@@ -1,382 +0,0 @@
|
||||
"""
|
||||
FP&A Test Data Generator
|
||||
Generates realistic CSV data for: Budget vs Actuals, Cash Flow, P&L, Headcount
|
||||
Covers 2 years (2023–2024), 4 departments, product & service revenue mix.
|
||||
"""
|
||||
|
||||
import csv
|
||||
import random
|
||||
import os
|
||||
from datetime import date, timedelta
|
||||
from dataclasses import dataclass, fields, asdict
|
||||
from typing import List
|
||||
|
||||
random.seed(42) # reproducible data
|
||||
|
||||
OUTPUT_DIR = os.path.join(os.path.dirname(__file__), "..", "data", "csv")
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
|
||||
# ── Company config ────────────────────────────────────────────────────────────
|
||||
COMPANY = "AcmeSaaS Inc."
|
||||
DEPARTMENTS = ["Engineering", "Sales", "Marketing", "Operations"]
|
||||
YEARS = [2023, 2024]
|
||||
|
||||
# Revenue split: product (SaaS subscriptions) vs service (consulting/support)
|
||||
PRODUCT_REVENUE_MIX = 0.70 # 70% product (recurring SaaS)
|
||||
SERVICE_REVENUE_MIX = 0.30 # 30% services
|
||||
|
||||
# Monthly growth rates (realistic SaaS-style)
|
||||
PRODUCT_MONTHLY_GROWTH = 0.025 # 2.5% MoM
|
||||
SERVICE_MONTHLY_GROWTH = 0.015 # 1.5% MoM
|
||||
|
||||
# Base monthly revenue ($)
|
||||
BASE_PRODUCT_REVENUE = 180_000
|
||||
BASE_SERVICE_REVENUE = 75_000
|
||||
|
||||
# Variance helpers — actuals deviate from budget by ±%
|
||||
def vary(value: float, pct: float = 0.08) -> float:
|
||||
"""Apply random variance to simulate actuals vs budget."""
|
||||
return round(value * (1 + random.uniform(-pct, pct)), 2)
|
||||
|
||||
def months_range(years: List[int]):
|
||||
for year in years:
|
||||
for month in range(1, 13):
|
||||
yield year, month
|
||||
|
||||
# ── 1. Revenue (Budget vs Actuals) ───────────────────────────────────────────
|
||||
@dataclass
|
||||
class RevenueRow:
|
||||
company: str
|
||||
year: int
|
||||
month: int
|
||||
period: str # e.g. "2023-01"
|
||||
revenue_type: str # "Product" | "Service"
|
||||
budget_amount: float
|
||||
actual_amount: float
|
||||
variance: float # actual - budget
|
||||
variance_pct: float # variance / budget * 100
|
||||
|
||||
def generate_revenue():
|
||||
rows = []
|
||||
prod_base = BASE_PRODUCT_REVENUE
|
||||
svc_base = BASE_SERVICE_REVENUE
|
||||
|
||||
for year, month in months_range(YEARS):
|
||||
period = f"{year}-{month:02d}"
|
||||
|
||||
for rev_type, base in [("Product", prod_base), ("Service", svc_base)]:
|
||||
budget = round(base, 2)
|
||||
actual = vary(budget, pct=0.10)
|
||||
variance = round(actual - budget, 2)
|
||||
vpct = round((variance / budget) * 100, 2) if budget else 0
|
||||
rows.append(RevenueRow(COMPANY, year, month, period, rev_type,
|
||||
budget, actual, variance, vpct))
|
||||
|
||||
# grow base each month
|
||||
prod_base *= (1 + PRODUCT_MONTHLY_GROWTH)
|
||||
svc_base *= (1 + SERVICE_MONTHLY_GROWTH)
|
||||
|
||||
write_csv("revenue_budget_vs_actuals.csv", rows)
|
||||
print(f" ✓ revenue_budget_vs_actuals.csv ({len(rows)} rows)")
|
||||
|
||||
# ── 2. Department Opex (Budget vs Actuals) ───────────────────────────────────
|
||||
DEPT_BUDGETS = {
|
||||
# (base_monthly_opex, growth_rate)
|
||||
"Engineering": (95_000, 0.012),
|
||||
"Sales": (70_000, 0.018),
|
||||
"Marketing": (55_000, 0.015),
|
||||
"Operations": (40_000, 0.008),
|
||||
}
|
||||
|
||||
OPEX_CATEGORIES = ["Salaries", "Software & Tools", "Travel", "Marketing Spend",
|
||||
"Cloud Infrastructure", "Contractors", "Office & Facilities"]
|
||||
|
||||
@dataclass
|
||||
class OpexRow:
|
||||
company: str
|
||||
department: str
|
||||
year: int
|
||||
month: int
|
||||
period: str
|
||||
category: str
|
||||
budget_amount: float
|
||||
actual_amount: float
|
||||
variance: float
|
||||
variance_pct: float
|
||||
|
||||
def generate_opex():
|
||||
rows = []
|
||||
dept_bases = {d: v[0] for d, v in DEPT_BUDGETS.items()}
|
||||
|
||||
for year, month in months_range(YEARS):
|
||||
period = f"{year}-{month:02d}"
|
||||
for dept, (_, growth) in DEPT_BUDGETS.items():
|
||||
total_budget = dept_bases[dept]
|
||||
# Split across categories with random weights
|
||||
weights = [random.uniform(0.05, 0.35) for _ in OPEX_CATEGORIES]
|
||||
total_w = sum(weights)
|
||||
weights = [w / total_w for w in weights]
|
||||
|
||||
for cat, w in zip(OPEX_CATEGORIES, weights):
|
||||
budget = round(total_budget * w, 2)
|
||||
actual = vary(budget, pct=0.12)
|
||||
variance = round(actual - budget, 2)
|
||||
vpct = round((variance / budget) * 100, 2) if budget else 0
|
||||
rows.append(OpexRow(COMPANY, dept, year, month, period, cat,
|
||||
budget, actual, variance, vpct))
|
||||
|
||||
dept_bases[dept] *= (1 + growth)
|
||||
|
||||
write_csv("opex_budget_vs_actuals.csv", rows)
|
||||
print(f" ✓ opex_budget_vs_actuals.csv ({len(rows)} rows)")
|
||||
|
||||
# ── 3. P&L / Income Statement ─────────────────────────────────────────────────
|
||||
@dataclass
|
||||
class PLRow:
|
||||
company: str
|
||||
year: int
|
||||
month: int
|
||||
period: str
|
||||
product_revenue: float
|
||||
service_revenue: float
|
||||
total_revenue: float
|
||||
cogs_product: float # ~25% of product rev
|
||||
cogs_service: float # ~45% of service rev (labor-heavy)
|
||||
total_cogs: float
|
||||
gross_profit: float
|
||||
gross_margin_pct: float
|
||||
total_opex: float
|
||||
ebitda: float
|
||||
ebitda_margin_pct: float
|
||||
net_income: float # after ~25% tax estimate
|
||||
|
||||
def generate_pl():
|
||||
rows = []
|
||||
prod_base = BASE_PRODUCT_REVENUE
|
||||
svc_base = BASE_SERVICE_REVENUE
|
||||
dept_bases = {d: v[0] for d, v in DEPT_BUDGETS.items()}
|
||||
|
||||
for year, month in months_range(YEARS):
|
||||
period = f"{year}-{month:02d}"
|
||||
|
||||
prod_rev = vary(prod_base, 0.08)
|
||||
svc_rev = vary(svc_base, 0.10)
|
||||
total_rev = round(prod_rev + svc_rev, 2)
|
||||
|
||||
cogs_prod = round(prod_rev * vary(0.25, 0.05), 2)
|
||||
cogs_svc = round(svc_rev * vary(0.45, 0.06), 2)
|
||||
total_cogs = round(cogs_prod + cogs_svc, 2)
|
||||
|
||||
gross_profit = round(total_rev - total_cogs, 2)
|
||||
gm_pct = round((gross_profit / total_rev) * 100, 2) if total_rev else 0
|
||||
|
||||
total_opex = round(sum(
|
||||
vary(dept_bases[d], 0.10) for d in DEPARTMENTS
|
||||
), 2)
|
||||
|
||||
ebitda = round(gross_profit - total_opex, 2)
|
||||
ebitda_pct = round((ebitda / total_rev) * 100, 2) if total_rev else 0
|
||||
net_income = round(ebitda * 0.75, 2) # rough 25% tax
|
||||
|
||||
rows.append(PLRow(
|
||||
COMPANY, year, month, period,
|
||||
round(prod_rev, 2), round(svc_rev, 2), total_rev,
|
||||
cogs_prod, cogs_svc, total_cogs,
|
||||
gross_profit, gm_pct,
|
||||
total_opex, ebitda, ebitda_pct, net_income
|
||||
))
|
||||
|
||||
prod_base *= (1 + PRODUCT_MONTHLY_GROWTH)
|
||||
svc_base *= (1 + SERVICE_MONTHLY_GROWTH)
|
||||
for d, (_, g) in DEPT_BUDGETS.items():
|
||||
dept_bases[d] *= (1 + g)
|
||||
|
||||
write_csv("pl_income_statement.csv", rows)
|
||||
print(f" ✓ pl_income_statement.csv ({len(rows)} rows)")
|
||||
|
||||
# ── 4. Cash Flow ──────────────────────────────────────────────────────────────
|
||||
@dataclass
|
||||
class CashFlowRow:
|
||||
company: str
|
||||
year: int
|
||||
month: int
|
||||
period: str
|
||||
# Operating
|
||||
cash_collected_product: float # product ARR collections (may lag revenue)
|
||||
cash_collected_service: float
|
||||
cash_paid_opex: float
|
||||
cash_paid_cogs: float
|
||||
net_operating_cash_flow: float
|
||||
# Investing
|
||||
capex: float # infra / hardware
|
||||
net_investing_cash_flow: float
|
||||
# Financing
|
||||
loan_repayment: float
|
||||
equity_raised: float
|
||||
net_financing_cash_flow: float
|
||||
# Summary
|
||||
net_change_in_cash: float
|
||||
closing_cash_balance: float
|
||||
|
||||
def generate_cashflow():
|
||||
rows = []
|
||||
cash_balance = 1_200_000.0 # starting cash (seed round runway)
|
||||
prod_base = BASE_PRODUCT_REVENUE
|
||||
svc_base = BASE_SERVICE_REVENUE
|
||||
dept_bases = {d: v[0] for d, v in DEPT_BUDGETS.items()}
|
||||
|
||||
for year, month in months_range(YEARS):
|
||||
period = f"{year}-{month:02d}"
|
||||
|
||||
# Collections slightly lag invoicing (DSO ~30 days effect)
|
||||
cash_prod = vary(prod_base * 0.95, 0.06)
|
||||
cash_svc = vary(svc_base * 0.90, 0.08) # services collect slower
|
||||
|
||||
opex_paid = sum(vary(dept_bases[d], 0.08) for d in DEPARTMENTS)
|
||||
cogs_paid = vary((prod_base * 0.25) + (svc_base * 0.45), 0.07)
|
||||
|
||||
net_op = round(cash_prod + cash_svc - opex_paid - cogs_paid, 2)
|
||||
|
||||
# Investing — occasional capex spikes
|
||||
capex = vary(8_000, 0.40) if random.random() > 0.4 else 0.0
|
||||
net_inv = round(-capex, 2)
|
||||
|
||||
# Financing — occasional loan repayment
|
||||
loan = vary(5_000, 0.20) if month % 3 == 0 else 0.0
|
||||
equity = 0.0
|
||||
if year == 2023 and month == 6:
|
||||
equity = 500_000.0 # Series A mid-2023
|
||||
net_fin = round(equity - loan, 2)
|
||||
|
||||
net_change = round(net_op + net_inv + net_fin, 2)
|
||||
cash_balance = round(cash_balance + net_change, 2)
|
||||
|
||||
rows.append(CashFlowRow(
|
||||
COMPANY, year, month, period,
|
||||
round(cash_prod, 2), round(cash_svc, 2),
|
||||
round(opex_paid, 2), round(cogs_paid, 2), net_op,
|
||||
round(capex, 2), net_inv,
|
||||
round(loan, 2), round(equity, 2), net_fin,
|
||||
net_change, cash_balance
|
||||
))
|
||||
|
||||
prod_base *= (1 + PRODUCT_MONTHLY_GROWTH)
|
||||
svc_base *= (1 + SERVICE_MONTHLY_GROWTH)
|
||||
for d, (_, g) in DEPT_BUDGETS.items():
|
||||
dept_bases[d] *= (1 + g)
|
||||
|
||||
write_csv("cash_flow.csv", rows)
|
||||
print(f" ✓ cash_flow.csv ({len(rows)} rows)")
|
||||
|
||||
# ── 5. Headcount & Workforce ──────────────────────────────────────────────────
|
||||
ROLES = {
|
||||
"Engineering": [("Software Engineer", 120_000), ("Senior Engineer", 160_000),
|
||||
("Engineering Manager", 180_000), ("DevOps Engineer", 130_000)],
|
||||
"Sales": [("Account Executive", 90_000), ("Sales Manager", 140_000),
|
||||
("SDR", 65_000)],
|
||||
"Marketing": [("Marketing Manager", 110_000), ("Content Strategist", 80_000),
|
||||
("Growth Analyst", 95_000)],
|
||||
"Operations": [("Operations Manager", 115_000), ("Customer Success", 75_000),
|
||||
("Finance Analyst", 95_000)],
|
||||
}
|
||||
|
||||
@dataclass
|
||||
class HeadcountRow:
|
||||
company: str
|
||||
employee_id: str
|
||||
department: str
|
||||
role: str
|
||||
hire_date: str
|
||||
termination_date: str # empty if active
|
||||
status: str # Active | Terminated
|
||||
annual_salary_budget: float
|
||||
actual_salary_paid_ytd: float # YTD for the given year
|
||||
year: int
|
||||
month: int
|
||||
period: str
|
||||
headcount_fte: float # 1.0 full time, 0.5 contractor etc.
|
||||
|
||||
def generate_headcount():
|
||||
rows = []
|
||||
emp_id = 1000
|
||||
employees = []
|
||||
|
||||
# Seed initial employees at start of 2023
|
||||
for dept, role_list in ROLES.items():
|
||||
# Start with 2-4 per department
|
||||
count = random.randint(2, 4)
|
||||
for _ in range(count):
|
||||
role, salary = random.choice(role_list)
|
||||
hire_date = date(2022, random.randint(1, 12), random.randint(1, 28))
|
||||
employees.append({
|
||||
"id": f"EMP{emp_id}",
|
||||
"dept": dept, "role": role, "salary": salary,
|
||||
"hire_date": hire_date, "term_date": None,
|
||||
"fte": 1.0,
|
||||
})
|
||||
emp_id += 1
|
||||
|
||||
for year, month in months_range(YEARS):
|
||||
period = f"{year}-{month:02d}"
|
||||
current = date(year, month, 1)
|
||||
|
||||
# Random hiring each month
|
||||
if random.random() > 0.55:
|
||||
dept = random.choice(DEPARTMENTS)
|
||||
role, salary = random.choice(ROLES[dept])
|
||||
employees.append({
|
||||
"id": f"EMP{emp_id}",
|
||||
"dept": dept, "role": role, "salary": salary,
|
||||
"hire_date": current, "term_date": None,
|
||||
"fte": random.choice([1.0, 1.0, 1.0, 0.5]), # mostly FT
|
||||
})
|
||||
emp_id += 1
|
||||
|
||||
# Occasional attrition
|
||||
active = [e for e in employees if e["term_date"] is None]
|
||||
if len(active) > 6 and random.random() > 0.85:
|
||||
leaver = random.choice(active)
|
||||
leaver["term_date"] = current
|
||||
|
||||
# Snapshot each employee for this month
|
||||
for emp in employees:
|
||||
if emp["hire_date"] > current:
|
||||
continue # not hired yet
|
||||
status = "Active" if emp["term_date"] is None or emp["term_date"] > current else "Terminated"
|
||||
months_in_year = month if emp["hire_date"].year < year else (
|
||||
month - emp["hire_date"].month + 1
|
||||
)
|
||||
months_in_year = max(0, min(months_in_year, month))
|
||||
ytd_paid = round((emp["salary"] / 12) * months_in_year * vary(1.0, 0.02), 2)
|
||||
rows.append(HeadcountRow(
|
||||
COMPANY, emp["id"], emp["dept"], emp["role"],
|
||||
str(emp["hire_date"]),
|
||||
str(emp["term_date"]) if emp["term_date"] else "",
|
||||
status, emp["salary"], ytd_paid,
|
||||
year, month, period, emp["fte"]
|
||||
))
|
||||
|
||||
write_csv("headcount_workforce.csv", rows)
|
||||
print(f" ✓ headcount_workforce.csv ({len(rows)} rows)")
|
||||
|
||||
# ── CSV writer ────────────────────────────────────────────────────────────────
|
||||
def write_csv(filename: str, rows: list):
|
||||
if not rows:
|
||||
return
|
||||
path = os.path.join(OUTPUT_DIR, filename)
|
||||
with open(path, "w", newline="") as f:
|
||||
writer = csv.DictWriter(f, fieldnames=[field.name for field in fields(rows[0])])
|
||||
writer.writeheader()
|
||||
writer.writerows([asdict(r) for r in rows])
|
||||
|
||||
# ── Entry point ───────────────────────────────────────────────────────────────
|
||||
if __name__ == "__main__":
|
||||
print(f"\n🏗 Generating FP&A test data for {COMPANY}")
|
||||
print(f" Periods : {YEARS[0]}-01 → {YEARS[-1]}-12 (24 months)")
|
||||
print(f" Depts : {', '.join(DEPARTMENTS)}\n")
|
||||
generate_revenue()
|
||||
generate_opex()
|
||||
generate_pl()
|
||||
generate_cashflow()
|
||||
generate_headcount()
|
||||
print(f"\n✅ All CSV files written to: {os.path.abspath(OUTPUT_DIR)}\n")
|
||||
@@ -1,10 +0,0 @@
|
||||
# FP&A Test Platform dependencies
|
||||
|
||||
# HTTP client — used by loaders/api_loader.py to POST to the Go API
|
||||
requests>=2.31.0,<3.0.0
|
||||
|
||||
# Needed by requests on some systems (usually installed transitively)
|
||||
certifi>=2023.7.22
|
||||
urllib3>=1.26.0,<3.0.0
|
||||
|
||||
# Note: tests/test_fpa.py uses stdlib only (csv, urllib, json) — no extra deps needed
|
||||
@@ -1,477 +0,0 @@
|
||||
"""
|
||||
FP&A Test Suite
|
||||
Run: python tests/test_fpa.py
|
||||
python tests/test_fpa.py --url http://localhost:9000
|
||||
"""
|
||||
|
||||
import csv, json, os, sys, traceback
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable, List, Optional
|
||||
import requests
|
||||
|
||||
DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "data", "csv")
|
||||
API_BASE = os.getenv("FPA_API_URL", "http://localhost:8080")
|
||||
|
||||
# ── Runner ────────────────────────────────────────────────────────────────────
|
||||
|
||||
@dataclass
|
||||
class Result:
|
||||
name: str
|
||||
passed: bool
|
||||
skipped: bool = False
|
||||
message: str = ""
|
||||
|
||||
class SkipTest(Exception):
|
||||
pass
|
||||
|
||||
def skip(reason: str):
|
||||
raise SkipTest(reason)
|
||||
|
||||
class Suite:
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
self.results: List[Result] = []
|
||||
|
||||
def run(self, label: str, fn: Callable):
|
||||
try:
|
||||
fn()
|
||||
self.results.append(Result(label, passed=True))
|
||||
except SkipTest as e:
|
||||
self.results.append(Result(label, passed=False, skipped=True, message=str(e)))
|
||||
except AssertionError as e:
|
||||
self.results.append(Result(label, passed=False, message=str(e)))
|
||||
except Exception as e:
|
||||
self.results.append(Result(label, passed=False,
|
||||
message=f"{type(e).__name__}: {e}\n{traceback.format_exc()}"))
|
||||
|
||||
# ── Assertions ────────────────────────────────────────────────────────────────
|
||||
|
||||
def ok(condition, msg=""):
|
||||
if not condition:
|
||||
raise AssertionError(msg or "Expected True")
|
||||
|
||||
def eq(actual, expected, msg=""):
|
||||
if actual != expected:
|
||||
raise AssertionError(msg or f"Expected {expected!r}, got {actual!r}")
|
||||
|
||||
def near(actual, expected, tol=1.0, msg=""):
|
||||
if abs(actual - expected) > tol:
|
||||
raise AssertionError(msg or f"Expected ~{expected:.2f}, got {actual:.2f} (tol={tol})")
|
||||
|
||||
# ── HTTP ──────────────────────────────────────────────────────────────────────
|
||||
|
||||
session = requests.Session()
|
||||
session.headers.update({"Content-Type": "application/json"})
|
||||
|
||||
def get(path: str, **params):
|
||||
r = session.get(f"{API_BASE}{path}", params=params, timeout=5)
|
||||
return r.status_code, r.json() if r.content else {}
|
||||
|
||||
def post(path: str, body: dict):
|
||||
r = session.post(f"{API_BASE}{path}", json=body, timeout=10)
|
||||
return r.status_code, r.json() if r.content else {}
|
||||
|
||||
def put(path: str, body: dict):
|
||||
r = session.put(f"{API_BASE}{path}", json=body, timeout=10)
|
||||
return r.status_code, r.json() if r.content else {}
|
||||
|
||||
def api_up() -> bool:
|
||||
try:
|
||||
status, _ = get("/api/v1/health")
|
||||
return status == 200
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
# ── CSV ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
def read_csv(filename: str) -> List[dict]:
|
||||
path = os.path.join(DATA_DIR, filename)
|
||||
if not os.path.exists(path):
|
||||
skip(f"{filename} not found — run generators/generate_data.py first")
|
||||
with open(path, newline="") as f:
|
||||
return list(csv.DictReader(f))
|
||||
|
||||
# ── Reference data ────────────────────────────────────────────────────────────
|
||||
# Seeded once via the API; IDs stored at runtime.
|
||||
|
||||
DEPT_IDS: dict[str, int] = {} # name → id
|
||||
GL_IDS: dict[str, int] = {} # code → id
|
||||
|
||||
DEPARTMENTS = [
|
||||
{"code": "REV", "name": "Revenue", "cost_center": "CC-100"},
|
||||
{"code": "ENG", "name": "Engineering", "cost_center": "CC-200"},
|
||||
{"code": "SAL", "name": "Sales", "cost_center": "CC-300"},
|
||||
{"code": "MKT", "name": "Marketing", "cost_center": "CC-400"},
|
||||
{"code": "OPS", "name": "Operations", "cost_center": "CC-500"},
|
||||
{"code": "FIN", "name": "Finance", "cost_center": "CC-600"},
|
||||
]
|
||||
|
||||
GL_ACCOUNTS = [
|
||||
{"code": "4000", "description": "SaaS Subscription Revenue", "type": "revenue", "favour_high": True},
|
||||
{"code": "4100", "description": "Professional Services Revenue", "type": "revenue", "favour_high": True},
|
||||
{"code": "5000", "description": "Cost of Goods — Product", "type": "cogs", "favour_high": False},
|
||||
{"code": "5100", "description": "Cost of Goods — Service", "type": "cogs", "favour_high": False},
|
||||
{"code": "6000", "description": "Salaries and Wages", "type": "opex", "favour_high": False},
|
||||
{"code": "6100", "description": "Software and SaaS Tools", "type": "opex", "favour_high": False},
|
||||
{"code": "6200", "description": "Travel and Expenses", "type": "opex", "favour_high": False},
|
||||
{"code": "6300", "description": "Marketing and Paid Media", "type": "opex", "favour_high": False},
|
||||
{"code": "6400", "description": "Cloud Infrastructure", "type": "opex", "favour_high": False},
|
||||
{"code": "6500", "description": "Contractors and Freelancers", "type": "opex", "favour_high": False},
|
||||
{"code": "6600", "description": "Office and Facilities", "type": "opex", "favour_high": False},
|
||||
{"code": "7000", "description": "Capital Expenditure", "type": "capex", "favour_high": False},
|
||||
{"code": "9200", "description": "Headcount Cost", "type": "headcount", "favour_high": False},
|
||||
]
|
||||
|
||||
# CSV category name → GL code
|
||||
CSV_TO_GL = {
|
||||
"Product": "4000",
|
||||
"Service": "4100",
|
||||
"Salaries": "6000",
|
||||
"Software & Tools": "6100",
|
||||
"Travel": "6200",
|
||||
"Marketing Spend": "6300",
|
||||
"Cloud Infrastructure": "6400",
|
||||
"Contractors": "6500",
|
||||
"Office & Facilities": "6600",
|
||||
"Headcount": "9200",
|
||||
}
|
||||
|
||||
def gl_id(category: str) -> int:
|
||||
code = CSV_TO_GL.get(category)
|
||||
assert code, f"No GL mapping for '{category}'"
|
||||
assert code in GL_IDS, f"GL '{code}' not seeded — call seed() first"
|
||||
return GL_IDS[code]
|
||||
|
||||
def gl_code(category: str) -> str:
|
||||
code = CSV_TO_GL.get(category)
|
||||
assert code, f"No GL mapping for '{category}'"
|
||||
return code
|
||||
|
||||
def fiscal(period: str):
|
||||
y, m = period.split("-")
|
||||
return int(y), int(m)
|
||||
|
||||
def seed():
|
||||
"""POST departments and GL accounts; cache returned IDs as int. Idempotent."""
|
||||
for d in DEPARTMENTS:
|
||||
status, body = post("/api/v1/departments", d)
|
||||
ok(status in (200, 201), f"POST /departments '{d['name']}' → {status}: {body}")
|
||||
ok("id" in body, f"departments response missing 'id': {body}")
|
||||
DEPT_IDS[d["name"]] = int(body["id"]) # explicit int — guards against "id": "3"
|
||||
|
||||
for g in GL_ACCOUNTS:
|
||||
status, body = post("/api/v1/gl-accounts", g)
|
||||
ok(status in (200, 201), f"POST /gl-accounts '{g['code']}' → {status}: {body}")
|
||||
ok("id" in body, f"gl-accounts response missing 'id': {body}")
|
||||
GL_IDS[g["code"]] = int(body["id"])
|
||||
|
||||
# ── suite_revenue ─────────────────────────────────────────────────────────────
|
||||
|
||||
def suite_revenue() -> Suite:
|
||||
s = Suite("Revenue — CSV + API round-trip")
|
||||
|
||||
if not api_up():
|
||||
s.run("API reachable", lambda: skip(f"Go API not running at {API_BASE}"))
|
||||
return s
|
||||
|
||||
rows = []
|
||||
|
||||
# 1. CSV validation
|
||||
s.run("CSV loads", lambda: rows.extend(read_csv("revenue_budget_vs_actuals.csv")))
|
||||
s.run("48 rows (24 months × 2 types)", lambda: eq(len(rows), 48))
|
||||
s.run("only Product and Service types", lambda:
|
||||
eq({r["revenue_type"] for r in rows}, {"Product", "Service"}))
|
||||
s.run("variance = actual − budget", lambda: [
|
||||
near(float(r["actual_amount"]) - float(r["budget_amount"]),
|
||||
float(r["variance"]), tol=0.01, msg=f"variance wrong in {r['period']}")
|
||||
for r in rows
|
||||
])
|
||||
|
||||
# 2. Seed FK parents
|
||||
s.run("seed departments + GL accounts", seed)
|
||||
|
||||
# 3. POST budgets
|
||||
budget_ids: dict[str, int] = {}
|
||||
|
||||
def post_budget(rev_type: str):
|
||||
row = next(r for r in rows if r["revenue_type"] == rev_type and r["period"] == "2023-01")
|
||||
fy, fp = fiscal(row["period"]) # fiscal() already returns (int, int)
|
||||
status, body = post("/api/v1/budgets", {
|
||||
"fiscal_year": int(fy),
|
||||
"fiscal_period": int(fp),
|
||||
"version": "original",
|
||||
"department_id": int(DEPT_IDS["Revenue"]),
|
||||
"gl_account_id": int(gl_id(rev_type)),
|
||||
"amount": float(row["budget_amount"]),
|
||||
"currency": "DKK",
|
||||
"notes": f"{rev_type} budget — csv import",
|
||||
"created_by": "test_suite",
|
||||
})
|
||||
# 201 = created, 200 = upserted (row already existed — idempotent re-run)
|
||||
ok(status in (200, 201), f"POST /budgets {rev_type} → {status}: {body}")
|
||||
ok(body.get("id"), f"response missing 'id': {body}")
|
||||
budget_ids[rev_type] = int(body["id"])
|
||||
|
||||
s.run("POST budget — Product 2023-01", lambda: post_budget("Product"))
|
||||
s.run("POST budget — Service 2023-01", lambda: post_budget("Service"))
|
||||
|
||||
# 4. POST actuals
|
||||
def post_actuals():
|
||||
for r in [r for r in rows if r["period"] == "2023-01"]:
|
||||
fy, fp = fiscal(r["period"])
|
||||
status, body = post("/api/v1/actuals/ingest", {
|
||||
"fiscal_year": int(fy),
|
||||
"fiscal_period": int(fp),
|
||||
"dept_code": "REV",
|
||||
"gl_code": gl_code(r["revenue_type"]),
|
||||
"amount": float(r["actual_amount"]),
|
||||
"currency": "DKK",
|
||||
"source": "test_suite",
|
||||
})
|
||||
ok(status in (200, 201),
|
||||
f"POST /actuals/ingest {r['revenue_type']} → {status}: {body}")
|
||||
|
||||
s.run("POST actuals — Product + Service 2023-01", post_actuals)
|
||||
|
||||
# 5. GET variance — verify VarianceReport shape and values
|
||||
report: dict = {}
|
||||
|
||||
def fetch_report():
|
||||
status, body = get("/api/v1/variance",
|
||||
fiscal_year=2023, fiscal_period=1, dept_code="REV", version="original")
|
||||
ok(status == 200, f"GET /variance → {status}: {body}")
|
||||
ok(isinstance(body, dict), "expected VarianceReport object")
|
||||
ok("lines" in body, f"missing 'lines', got keys: {list(body.keys())}")
|
||||
ok(len(body["lines"]) > 0, "lines is empty")
|
||||
report.update(body)
|
||||
|
||||
s.run("GET /variance returns VarianceReport", fetch_report)
|
||||
|
||||
def verify_report():
|
||||
eq(report["fiscal_year"], 2023, "fiscal_year")
|
||||
eq(report["fiscal_period"], 1, "fiscal_period")
|
||||
eq(report["version"], "original", "version")
|
||||
for field in ("department", "currency", "total_budget", "total_actual"):
|
||||
ok(field in report, f"VarianceReport missing '{field}'")
|
||||
|
||||
for line in report["lines"]:
|
||||
for f in ("gl_account_id", "gl_description", "gl_type",
|
||||
"budget", "actual", "variance_abs", "status"):
|
||||
ok(f in line, f"VarianceLine missing '{f}'")
|
||||
ok(line["status"] in ("favourable", "unfavourable", "on_budget"),
|
||||
f"bad status: {line['status']}")
|
||||
|
||||
s.run("VarianceReport shape valid", verify_report)
|
||||
|
||||
def verify_product_line():
|
||||
csv_row = next(r for r in rows
|
||||
if r["revenue_type"] == "Product" and r["period"] == "2023-01")
|
||||
line = next((l for l in report["lines"]
|
||||
if l["gl_account_id"] == "4000"), None)
|
||||
ok(line, f"no line for gl_account_id=4000, got: {[l['gl_account_id'] for l in report['lines']]}")
|
||||
near(float(line["budget"]), float(csv_row["budget_amount"]), msg="budget")
|
||||
near(float(line["actual"]), float(csv_row["actual_amount"]), msg="actual")
|
||||
near(float(line["variance_abs"]), abs(float(csv_row["variance"])), msg="variance_abs")
|
||||
if float(csv_row["actual_amount"]) >= float(csv_row["budget_amount"]):
|
||||
eq(line["status"], "favourable", "revenue over-budget should be favourable")
|
||||
|
||||
s.run("Product line values match CSV", verify_product_line)
|
||||
|
||||
s.run("totals = sum of lines", lambda: (
|
||||
near(float(report["total_budget"]),
|
||||
sum(float(l["budget"]) for l in report["lines"]), msg="total_budget"),
|
||||
near(float(report["total_actual"]),
|
||||
sum(float(l["actual"]) for l in report["lines"]), msg="total_actual"),
|
||||
))
|
||||
|
||||
# 6. Alerts shape check
|
||||
def check_alerts():
|
||||
status, body = get("/api/v1/variance/alerts", fiscal_year=2023, fiscal_period=1)
|
||||
ok(status == 200, f"GET /variance/alerts → {status}: {body}")
|
||||
alerts = body if isinstance(body, list) else body.get("alerts", [])
|
||||
for a in alerts:
|
||||
for f in ("gl_code", "description", "budget", "actual", "variance_pct", "status", "department"):
|
||||
ok(f in a, f"AlertThreshold missing '{f}'")
|
||||
ok(a["status"] in ("favourable", "unfavourable", "on_budget"),
|
||||
f"bad alert status: {a['status']}")
|
||||
|
||||
s.run("GET /variance/alerts shape valid", check_alerts)
|
||||
|
||||
# 7. Update to forecast_1 and verify variance shifts
|
||||
def update_forecast():
|
||||
bid = budget_ids.get("Product")
|
||||
if not bid:
|
||||
skip("no Product budget id — skipping")
|
||||
|
||||
csv_row = next(r for r in rows
|
||||
if r["revenue_type"] == "Product" and r["period"] == "2023-01")
|
||||
revised = float(csv_row["budget_amount"]) * 1.10
|
||||
|
||||
fy, fp = fiscal("2023-01")
|
||||
status, body = put(f"/api/v1/budgets/{bid}", {
|
||||
"fiscal_year": int(fy),
|
||||
"fiscal_period": int(fp),
|
||||
"version": "forecast_1",
|
||||
"department_id": int(DEPT_IDS["Revenue"]),
|
||||
"gl_account_id": int(gl_id("Product")),
|
||||
"amount": float(revised),
|
||||
"currency": "DKK",
|
||||
"notes": "Q1 reforecast +10%",
|
||||
"created_by": "test_suite",
|
||||
})
|
||||
ok(status in (200, 204), f"PUT /budgets/{bid} → {status}: {body}")
|
||||
|
||||
status2, body2 = get("/api/v1/variance",
|
||||
fiscal_year=2023, fiscal_period=1, dept_code="REV", version="forecast_1")
|
||||
ok(status2 == 200, f"GET forecast_1 variance → {status2}")
|
||||
lines = body2.get("lines", []) if isinstance(body2, dict) else []
|
||||
line = next((l for l in lines if l.get("gl_account_id") == "4000"), None)
|
||||
ok(line, "Product line missing in forecast_1 variance")
|
||||
near(float(line["budget"]), revised, msg="forecast_1 budget not updated")
|
||||
|
||||
s.run("PUT budget forecast_1 — variance reflects revision", update_forecast)
|
||||
|
||||
return s
|
||||
|
||||
# ── CSV-only suites ───────────────────────────────────────────────────────────
|
||||
|
||||
def suite_opex() -> Suite:
|
||||
s = Suite("Opex CSV")
|
||||
rows = []
|
||||
s.run("loads", lambda: rows.extend(read_csv("opex_budget_vs_actuals.csv")))
|
||||
s.run("has rows", lambda: ok(len(rows) > 0))
|
||||
s.run("all departments present", lambda:
|
||||
ok({"Engineering","Sales","Marketing","Operations"}.issubset({r["department"] for r in rows})))
|
||||
s.run("no zero budgets", lambda: [
|
||||
ok(float(r["budget_amount"]) > 0, f"zero budget: {r['department']}/{r['category']}")
|
||||
for r in rows
|
||||
])
|
||||
s.run("variance sign correct", lambda: [
|
||||
eq(1 if float(r["variance"]) > 0 else (-1 if float(r["variance"]) < 0 else 0),
|
||||
1 if float(r["actual_amount"]) > float(r["budget_amount"]) else
|
||||
(-1 if float(r["actual_amount"]) < float(r["budget_amount"]) else 0),
|
||||
f"sign wrong {r['period']} {r['category']}")
|
||||
for r in rows
|
||||
])
|
||||
return s
|
||||
|
||||
|
||||
def suite_pl() -> Suite:
|
||||
s = Suite("P&L CSV")
|
||||
rows = []
|
||||
s.run("loads", lambda: rows.extend(read_csv("pl_income_statement.csv")))
|
||||
s.run("24 rows", lambda: eq(len(rows), 24))
|
||||
s.run("total_revenue = product + service", lambda: [
|
||||
near(float(r["product_revenue"]) + float(r["service_revenue"]),
|
||||
float(r["total_revenue"]), tol=0.05, msg=r["period"])
|
||||
for r in rows
|
||||
])
|
||||
s.run("gross_profit = revenue − cogs", lambda: [
|
||||
near(float(r["total_revenue"]) - float(r["total_cogs"]),
|
||||
float(r["gross_profit"]), tol=0.05, msg=r["period"])
|
||||
for r in rows
|
||||
])
|
||||
s.run("gross margin 30–90%", lambda: [
|
||||
ok(30 <= float(r["gross_margin_pct"]) <= 90, f"{r['gross_margin_pct']}% in {r['period']}")
|
||||
for r in rows
|
||||
])
|
||||
s.run("ebitda = gross_profit − opex", lambda: [
|
||||
near(float(r["gross_profit"]) - float(r["total_opex"]),
|
||||
float(r["ebitda"]), tol=0.05, msg=r["period"])
|
||||
for r in rows
|
||||
])
|
||||
return s
|
||||
|
||||
|
||||
def suite_cashflow() -> Suite:
|
||||
s = Suite("Cash Flow CSV")
|
||||
rows = []
|
||||
s.run("loads", lambda: rows.extend(read_csv("cash_flow.csv")))
|
||||
s.run("24 rows", lambda: eq(len(rows), 24))
|
||||
s.run("net_change = op + inv + fin", lambda: [
|
||||
near(float(r["net_operating_cash_flow"]) +
|
||||
float(r["net_investing_cash_flow"]) +
|
||||
float(r["net_financing_cash_flow"]),
|
||||
float(r["net_change_in_cash"]), tol=0.05, msg=r["period"])
|
||||
for r in rows
|
||||
])
|
||||
s.run("positive cash pre Series A", lambda: [
|
||||
ok(float(r["closing_cash_balance"]) > 0, f"negative cash in {r['period']}")
|
||||
for r in rows if r["period"] <= "2023-05"
|
||||
])
|
||||
s.run("Series A in 2023-06", lambda: (
|
||||
lambda june=next((r for r in rows if r["period"] == "2023-06"), None): (
|
||||
ok(june is not None, "2023-06 missing"),
|
||||
ok(float(june["equity_raised"]) > 0, "Series A not recorded"),
|
||||
)
|
||||
)())
|
||||
return s
|
||||
|
||||
|
||||
def suite_headcount() -> Suite:
|
||||
s = Suite("Headcount CSV")
|
||||
rows = []
|
||||
s.run("loads", lambda: rows.extend(read_csv("headcount_workforce.csv")))
|
||||
s.run("has rows", lambda: ok(len(rows) > 0))
|
||||
s.run("valid statuses", lambda:
|
||||
ok({r["status"] for r in rows}.issubset({"Active", "Terminated"})))
|
||||
s.run("FTE 0–1", lambda: [
|
||||
ok(0 < float(r["headcount_fte"]) <= 1.0, f"FTE={r['headcount_fte']} for {r['employee_id']}")
|
||||
for r in rows
|
||||
])
|
||||
s.run("salaries positive", lambda: [
|
||||
ok(float(r["annual_salary_budget"]) > 0) for r in rows
|
||||
])
|
||||
s.run("headcount grows Jan→Dec", lambda: ok(
|
||||
len([r for r in rows if r["period"] == "2024-12" and r["status"] == "Active"]) >=
|
||||
len([r for r in rows if r["period"] == "2023-01" and r["status"] == "Active"])
|
||||
))
|
||||
return s
|
||||
|
||||
# ── Reporter ──────────────────────────────────────────────────────────────────
|
||||
|
||||
def run_suites(suites):
|
||||
total = passed = failed = skipped = 0
|
||||
for suite in suites:
|
||||
print(f"\n\033[1m{suite.name}\033[0m")
|
||||
for r in suite.results:
|
||||
total += 1
|
||||
if r.skipped:
|
||||
skipped += 1
|
||||
print(f" \033[33m⊘\033[0m {r.name}")
|
||||
print(f" {r.message}")
|
||||
elif r.passed:
|
||||
passed += 1
|
||||
print(f" \033[32m✓\033[0m {r.name}")
|
||||
else:
|
||||
failed += 1
|
||||
print(f" \033[31m✗\033[0m {r.name}")
|
||||
for line in r.message.strip().splitlines():
|
||||
print(f" {line}")
|
||||
|
||||
print(f"\n{'─'*48}")
|
||||
color = "\033[32m" if failed == 0 else "\033[31m"
|
||||
print(f"{color}\033[1m{passed}/{total} passed\033[0m"
|
||||
+ (f" \033[33m⊘\033[0m {skipped} skipped" if skipped else "")
|
||||
+ (f" \033[31m✗\033[0m {failed} failed" if failed else ""))
|
||||
print()
|
||||
return failed == 0
|
||||
|
||||
# ── Entry point ───────────────────────────────────────────────────────────────
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description="FP&A test suite")
|
||||
parser.add_argument("--url", default=None, help="API base URL")
|
||||
args = parser.parse_args()
|
||||
if args.url:
|
||||
API_BASE = args.url
|
||||
|
||||
ok = run_suites([
|
||||
suite_revenue(),
|
||||
suite_opex(),
|
||||
suite_pl(),
|
||||
suite_cashflow(),
|
||||
suite_headcount(),
|
||||
])
|
||||
sys.exit(0 if ok else 1)
|
||||
268
tests/actual_test.go
Normal file
268
tests/actual_test.go
Normal file
@@ -0,0 +1,268 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"Engine/internal/database"
|
||||
"Engine/internal/handler"
|
||||
"Engine/internal/service"
|
||||
"Engine/tests/internal/testutil"
|
||||
)
|
||||
|
||||
// ── wire helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
type testServer struct {
|
||||
srv *httptest.Server
|
||||
db interface{ Close() error }
|
||||
}
|
||||
|
||||
func newFullServer(t *testing.T) *httptest.Server {
|
||||
t.Helper()
|
||||
db := testutil.NewTestDB(t)
|
||||
|
||||
actualsRepo := database.NewActualsRepo(db)
|
||||
actualsH := handler.NewActualsHandler(actualsRepo)
|
||||
|
||||
budgetRepo := database.NewBudgetRepo(db)
|
||||
varianceH := handler.NewVarianceHandler(service.NewVarianceService(budgetRepo, actualsRepo))
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("POST /api/v1/actuals/ingest", actualsH.Ingest)
|
||||
mux.HandleFunc("GET /api/v1/variance", varianceH.Report)
|
||||
mux.HandleFunc("GET /api/v1/variance/alerts", varianceH.Alerts)
|
||||
|
||||
srv := httptest.NewServer(mux)
|
||||
t.Cleanup(srv.Close)
|
||||
return srv
|
||||
}
|
||||
|
||||
func newActualsHandler(t *testing.T) *handler.ActualsHandler {
|
||||
t.Helper()
|
||||
return handler.NewActualsHandler(database.NewActualsRepo(testutil.NewTestDB(t)))
|
||||
}
|
||||
|
||||
// validActual returns one well-formed actual record.
|
||||
func validActual() map[string]any {
|
||||
return map[string]any{
|
||||
"department_id": 1,
|
||||
"gl_account_id": 1,
|
||||
"period": "2024-01",
|
||||
"amount": 1234.56,
|
||||
"source": "csv_import",
|
||||
}
|
||||
}
|
||||
|
||||
// ── Actuals: Ingest ───────────────────────────────────────────────────────────
|
||||
|
||||
func TestIngestActuals_SingleRecord(t *testing.T) {
|
||||
h := newActualsHandler(t)
|
||||
|
||||
w := testutil.Do(t, http.HandlerFunc(h.Ingest), http.MethodPost, "/",
|
||||
[]any{validActual()})
|
||||
|
||||
testutil.AssertStatus(t, w, http.StatusCreated)
|
||||
}
|
||||
|
||||
func TestIngestActuals_MultipleRecords(t *testing.T) {
|
||||
h := newActualsHandler(t)
|
||||
|
||||
records := []any{
|
||||
map[string]any{"department_id": 1, "gl_account_id": 1, "period": "2024-01", "amount": 100.00, "source": "csv"},
|
||||
map[string]any{"department_id": 1, "gl_account_id": 2, "period": "2024-01", "amount": 200.00, "source": "csv"},
|
||||
map[string]any{"department_id": 2, "gl_account_id": 1, "period": "2024-01", "amount": 300.00, "source": "csv"},
|
||||
}
|
||||
|
||||
w := testutil.Do(t, http.HandlerFunc(h.Ingest), http.MethodPost, "/", records)
|
||||
testutil.AssertStatus(t, w, http.StatusCreated)
|
||||
}
|
||||
|
||||
func TestIngestActuals_EmptyList(t *testing.T) {
|
||||
h := newActualsHandler(t)
|
||||
|
||||
w := testutil.Do(t, http.HandlerFunc(h.Ingest), http.MethodPost, "/", []any{})
|
||||
// Depending on your handler: 201 with 0 rows ingested, or 400
|
||||
t.Logf("empty ingest: %d — verify against your handler", w.Code)
|
||||
}
|
||||
|
||||
func TestIngestActuals_InvalidJSON(t *testing.T) {
|
||||
h := newActualsHandler(t)
|
||||
w := testutil.Do(t, http.HandlerFunc(h.Ingest), http.MethodPost, "/", nil)
|
||||
testutil.AssertStatus(t, w, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func TestIngestActuals_MissingPeriod(t *testing.T) {
|
||||
h := newActualsHandler(t)
|
||||
record := validActual()
|
||||
delete(record, "period")
|
||||
w := testutil.Do(t, http.HandlerFunc(h.Ingest), http.MethodPost, "/", []any{record})
|
||||
testutil.AssertStatus(t, w, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func TestIngestActuals_NegativeAmount(t *testing.T) {
|
||||
h := newActualsHandler(t)
|
||||
record := validActual()
|
||||
record["amount"] = -500.00
|
||||
w := testutil.Do(t, http.HandlerFunc(h.Ingest), http.MethodPost, "/", []any{record})
|
||||
// Adjust to match your validation rule
|
||||
testutil.AssertStatus(t, w, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func TestIngestActuals_Idempotent(t *testing.T) {
|
||||
h := newActualsHandler(t)
|
||||
fn := http.HandlerFunc(h.Ingest)
|
||||
|
||||
testutil.Do(t, fn, http.MethodPost, "/", []any{validActual()})
|
||||
// Ingesting the same record twice should not panic or 500
|
||||
w := testutil.Do(t, fn, http.MethodPost, "/", []any{validActual()})
|
||||
if w.Code >= 500 {
|
||||
t.Errorf("duplicate ingest returned %d, expected non-5xx", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Variance: Report ──────────────────────────────────────────────────────────
|
||||
|
||||
func TestVarianceReport_Empty(t *testing.T) {
|
||||
srv := newFullServer(t)
|
||||
|
||||
resp, err := srv.Client().Get(srv.URL + "/api/v1/variance?period=2024-01")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("variance report: got %d, want 200", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVarianceReport_WithData(t *testing.T) {
|
||||
srv := newFullServer(t)
|
||||
client := srv.Client()
|
||||
|
||||
// Seed a budget
|
||||
budgetPayload := map[string]any{
|
||||
"department_id": 1, "gl_account_id": 1, "period": "2024-01", "amount": 10000.00,
|
||||
}
|
||||
resp, _ := client.Post(srv.URL+"/api/v1/budgets", "application/json",
|
||||
mustJSON(t, budgetPayload))
|
||||
resp.Body.Close()
|
||||
|
||||
// Ingest an actual — under budget
|
||||
actualPayload := []any{
|
||||
map[string]any{"department_id": 1, "gl_account_id": 1, "period": "2024-01", "amount": 7500.00, "source": "test"},
|
||||
}
|
||||
resp2, _ := client.Post(srv.URL+"/api/v1/actuals/ingest", "application/json",
|
||||
mustJSON(t, actualPayload))
|
||||
resp2.Body.Close()
|
||||
|
||||
// Get variance report
|
||||
resp3, err := client.Get(srv.URL + "/api/v1/variance?period=2024-01")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp3.Body.Close()
|
||||
|
||||
if resp3.StatusCode != http.StatusOK {
|
||||
t.Errorf("variance report: got %d, want 200", resp3.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVarianceReport_MissingPeriodParam(t *testing.T) {
|
||||
srv := newFullServer(t)
|
||||
|
||||
resp, err := srv.Client().Get(srv.URL + "/api/v1/variance")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
// Should return 400 if period is required, or 200 with all periods
|
||||
t.Logf("variance without period param: %d — verify against your handler", resp.StatusCode)
|
||||
}
|
||||
|
||||
// ── Variance: Alerts ──────────────────────────────────────────────────────────
|
||||
|
||||
func TestVarianceAlerts_NoAlerts(t *testing.T) {
|
||||
srv := newFullServer(t)
|
||||
|
||||
resp, err := srv.Client().Get(srv.URL + "/api/v1/variance/alerts?period=2024-01")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Errorf("alerts: got %d, want 200", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVarianceAlerts_OverBudgetTriggersAlert(t *testing.T) {
|
||||
srv := newFullServer(t)
|
||||
client := srv.Client()
|
||||
|
||||
// Budget: 1000
|
||||
budgetPayload := map[string]any{
|
||||
"department_id": 1, "gl_account_id": 1, "period": "2024-02", "amount": 1000.00,
|
||||
}
|
||||
resp, _ := client.Post(srv.URL+"/api/v1/budgets", "application/json",
|
||||
mustJSON(t, budgetPayload))
|
||||
resp.Body.Close()
|
||||
|
||||
// Actual: 1500 — 50% over budget
|
||||
actualPayload := []any{
|
||||
map[string]any{"department_id": 1, "gl_account_id": 1, "period": "2024-02", "amount": 1500.00, "source": "test"},
|
||||
}
|
||||
resp2, _ := client.Post(srv.URL+"/api/v1/actuals/ingest", "application/json",
|
||||
mustJSON(t, actualPayload))
|
||||
resp2.Body.Close()
|
||||
|
||||
resp3, err := client.Get(srv.URL + "/api/v1/variance/alerts?period=2024-02")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp3.Body.Close()
|
||||
|
||||
if resp3.StatusCode != http.StatusOK {
|
||||
t.Errorf("alerts: got %d, want 200", resp3.StatusCode)
|
||||
}
|
||||
|
||||
// Decode whatever shape your alerts response is
|
||||
var alerts any
|
||||
if err := json.NewDecoder(resp3.Body).Decode(&alerts); err != nil {
|
||||
t.Fatalf("decode alerts: %v", err)
|
||||
}
|
||||
t.Logf("alerts payload: %+v", alerts)
|
||||
}
|
||||
|
||||
func TestVarianceAlerts_UnderBudgetNoAlert(t *testing.T) {
|
||||
srv := newFullServer(t)
|
||||
client := srv.Client()
|
||||
|
||||
// Budget: 5000, Actual: 1000 — well under, no alert expected
|
||||
budgetPayload := map[string]any{
|
||||
"department_id": 2, "gl_account_id": 2, "period": "2024-03", "amount": 5000.00,
|
||||
}
|
||||
resp, _ := client.Post(srv.URL+"/api/v1/budgets", "application/json",
|
||||
mustJSON(t, budgetPayload))
|
||||
resp.Body.Close()
|
||||
|
||||
actualPayload := []any{
|
||||
map[string]any{"department_id": 2, "gl_account_id": 2, "period": "2024-03", "amount": 1000.00, "source": "test"},
|
||||
}
|
||||
resp2, _ := client.Post(srv.URL+"/api/v1/actuals/ingest", "application/json",
|
||||
mustJSON(t, actualPayload))
|
||||
resp2.Body.Close()
|
||||
|
||||
resp3, err := client.Get(srv.URL + "/api/v1/variance/alerts?period=2024-03")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp3.Body.Close()
|
||||
|
||||
if resp3.StatusCode != http.StatusOK {
|
||||
t.Errorf("alerts: got %d, want 200", resp3.StatusCode)
|
||||
}
|
||||
}
|
||||
259
tests/budget_test.go
Normal file
259
tests/budget_test.go
Normal file
@@ -0,0 +1,259 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"Engine/internal/database"
|
||||
"Engine/internal/handler"
|
||||
"Engine/internal/model"
|
||||
"Engine/internal/service"
|
||||
"Engine/tests/internal/testutil"
|
||||
)
|
||||
|
||||
// ── wire helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
func newBudgetServer(t *testing.T) *httptest.Server {
|
||||
t.Helper()
|
||||
db := testutil.NewTestDB(t)
|
||||
repo := database.NewBudgetRepo(db)
|
||||
h := handler.NewBudgetHandler(service.NewBudgetService(repo))
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("POST /api/v1/budgets", h.Create)
|
||||
mux.HandleFunc("PUT /api/v1/budgets/{id}", h.Update)
|
||||
mux.HandleFunc("DELETE /api/v1/budgets/{id}", h.Delete)
|
||||
|
||||
srv := httptest.NewServer(mux)
|
||||
t.Cleanup(srv.Close)
|
||||
return srv
|
||||
}
|
||||
|
||||
func newBudgetHandler(t *testing.T) *handler.BudgetHandler {
|
||||
t.Helper()
|
||||
return handler.NewBudgetHandler(service.NewBudgetService(database.NewBudgetRepo(testutil.NewTestDB(t))))
|
||||
}
|
||||
|
||||
func validBudget() map[string]any {
|
||||
return map[string]any{
|
||||
"fiscal_year": 2024,
|
||||
"fiscal_period": 1,
|
||||
"version": "original", // adjust to match your BudgetVersion values
|
||||
"department_id": 1,
|
||||
"gl_account_id": 1,
|
||||
"amount": 5000.00,
|
||||
"currency": "USD",
|
||||
"notes": "",
|
||||
"created_by": "test",
|
||||
}
|
||||
}
|
||||
|
||||
// ── Create ────────────────────────────────────────────────────────────────────
|
||||
|
||||
func TestCreateBudget_OK(t *testing.T) {
|
||||
h := newBudgetHandler(t)
|
||||
|
||||
w := testutil.Do(t, http.HandlerFunc(h.Create), http.MethodPost, "/", validBudget())
|
||||
testutil.AssertStatus(t, w, http.StatusCreated)
|
||||
|
||||
var got model.Budget
|
||||
testutil.DecodeJSON(t, w, &got)
|
||||
if got.ID == 0 {
|
||||
t.Error("expected non-zero ID")
|
||||
}
|
||||
if got.Amount != 5000.00 {
|
||||
t.Errorf("amount: got %v, want 5000.00", got.Amount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateBudget_InvalidJSON(t *testing.T) {
|
||||
h := newBudgetHandler(t)
|
||||
w := testutil.Do(t, http.HandlerFunc(h.Create), http.MethodPost, "/", nil)
|
||||
testutil.AssertStatus(t, w, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func TestCreateBudget_MissingPeriod(t *testing.T) {
|
||||
h := newBudgetHandler(t)
|
||||
body := validBudget()
|
||||
delete(body, "fiscal_period")
|
||||
w := testutil.Do(t, http.HandlerFunc(h.Create), http.MethodPost, "/", body)
|
||||
testutil.AssertStatus(t, w, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func TestCreateBudget_ZeroAmount(t *testing.T) {
|
||||
h := newBudgetHandler(t)
|
||||
body := validBudget()
|
||||
body["amount"] = 0
|
||||
w := testutil.Do(t, http.HandlerFunc(h.Create), http.MethodPost, "/", body)
|
||||
// Whether 0 is rejected or accepted depends on your business rule — adjust to match
|
||||
t.Logf("zero amount response: %d — verify against your handler", w.Code)
|
||||
}
|
||||
|
||||
func TestCreateBudget_NegativeAmount(t *testing.T) {
|
||||
h := newBudgetHandler(t)
|
||||
body := validBudget()
|
||||
body["amount"] = -100
|
||||
w := testutil.Do(t, http.HandlerFunc(h.Create), http.MethodPost, "/", body)
|
||||
testutil.AssertStatus(t, w, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
// ── Update ────────────────────────────────────────────────────────────────────
|
||||
|
||||
func TestUpdateBudget_OK(t *testing.T) {
|
||||
srv := newBudgetServer(t)
|
||||
client := srv.Client()
|
||||
|
||||
// Create first
|
||||
resp, err := client.Post(srv.URL+"/api/v1/budgets", "application/json",
|
||||
mustJSON(t, validBudget()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var created model.Budget
|
||||
json.NewDecoder(resp.Body).Decode(&created)
|
||||
resp.Body.Close()
|
||||
|
||||
// Update amount
|
||||
updated := validBudget()
|
||||
updated["amount"] = 9999.99
|
||||
|
||||
req, _ := http.NewRequest(http.MethodPut,
|
||||
fmt.Sprintf("%s/api/v1/budgets/%d", srv.URL, created.ID),
|
||||
mustJSON(t, updated))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp2, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp2.Body.Close()
|
||||
|
||||
if resp2.StatusCode != http.StatusOK && resp2.StatusCode != http.StatusNoContent {
|
||||
t.Errorf("update: got %d, want 200 or 204", resp2.StatusCode)
|
||||
}
|
||||
|
||||
if resp2.StatusCode == http.StatusOK {
|
||||
var got model.Budget
|
||||
json.NewDecoder(resp2.Body).Decode(&got)
|
||||
if got.Amount != 9999.99 {
|
||||
t.Errorf("updated amount: got %v, want 9999.99", got.Amount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateBudget_NotFound(t *testing.T) {
|
||||
srv := newBudgetServer(t)
|
||||
|
||||
req, _ := http.NewRequest(http.MethodPut, srv.URL+"/api/v1/budgets/9999",
|
||||
mustJSON(t, validBudget()))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := srv.Client().Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusNotFound && resp.StatusCode != http.StatusNoContent {
|
||||
t.Errorf("update non-existent: got %d, want 404 or 204", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateBudget_InvalidJSON(t *testing.T) {
|
||||
srv := newBudgetServer(t)
|
||||
|
||||
// Create one first so the ID exists
|
||||
resp, _ := srv.Client().Post(srv.URL+"/api/v1/budgets", "application/json",
|
||||
mustJSON(t, validBudget()))
|
||||
var created model.Budget
|
||||
json.NewDecoder(resp.Body).Decode(&created)
|
||||
resp.Body.Close()
|
||||
|
||||
req, _ := http.NewRequest(http.MethodPut,
|
||||
fmt.Sprintf("%s/api/v1/budgets/%d", srv.URL, created.ID),
|
||||
bytes.NewBufferString("not-json"))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp2, err := srv.Client().Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp2.Body.Close()
|
||||
if resp2.StatusCode != http.StatusBadRequest {
|
||||
t.Errorf("invalid JSON update: got %d, want 400", resp2.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Delete ────────────────────────────────────────────────────────────────────
|
||||
|
||||
func TestDeleteBudget_OK(t *testing.T) {
|
||||
srv := newBudgetServer(t)
|
||||
client := srv.Client()
|
||||
|
||||
resp, err := client.Post(srv.URL+"/api/v1/budgets", "application/json",
|
||||
mustJSON(t, validBudget()))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var created model.Budget
|
||||
json.NewDecoder(resp.Body).Decode(&created)
|
||||
resp.Body.Close()
|
||||
|
||||
req, _ := http.NewRequest(http.MethodDelete,
|
||||
srv.URL+"/api/v1/budgets/"+strconv.Itoa(created.ID), nil)
|
||||
resp2, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp2.Body.Close()
|
||||
|
||||
if resp2.StatusCode != http.StatusNoContent && resp2.StatusCode != http.StatusOK {
|
||||
t.Errorf("delete: got %d, want 200 or 204", resp2.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteBudget_NotFound(t *testing.T) {
|
||||
srv := newBudgetServer(t)
|
||||
|
||||
req, _ := http.NewRequest(http.MethodDelete, srv.URL+"/api/v1/budgets/9999", nil)
|
||||
resp, err := srv.Client().Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusNotFound && resp.StatusCode != http.StatusNoContent {
|
||||
t.Errorf("delete non-existent: got %d, want 404 or 204", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteBudget_DoubleDelete(t *testing.T) {
|
||||
srv := newBudgetServer(t)
|
||||
client := srv.Client()
|
||||
|
||||
resp, _ := client.Post(srv.URL+"/api/v1/budgets", "application/json",
|
||||
mustJSON(t, validBudget()))
|
||||
var created model.Budget
|
||||
json.NewDecoder(resp.Body).Decode(&created)
|
||||
resp.Body.Close()
|
||||
|
||||
url := srv.URL + "/api/v1/budgets/" + strconv.Itoa(created.ID)
|
||||
|
||||
req1, _ := http.NewRequest(http.MethodDelete, url, nil)
|
||||
resp1, _ := client.Do(req1)
|
||||
resp1.Body.Close()
|
||||
|
||||
// Second delete — should not panic, should return 404 or 204
|
||||
req2, _ := http.NewRequest(http.MethodDelete, url, nil)
|
||||
resp2, err := client.Do(req2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp2.Body.Close()
|
||||
|
||||
if resp2.StatusCode != http.StatusNotFound && resp2.StatusCode != http.StatusNoContent {
|
||||
t.Errorf("double delete: got %d, want 404 or 204", resp2.StatusCode)
|
||||
}
|
||||
}
|
||||
133
tests/internal/testutil/testutil.go
Normal file
133
tests/internal/testutil/testutil.go
Normal file
@@ -0,0 +1,133 @@
|
||||
package testutil
|
||||
|
||||
// Package testutil provides test helpers for handler and integration tests.
|
||||
// It wires up an in-memory SQLite database, a lightweight HTTP request helper,
|
||||
// and common assertion utilities.
|
||||
|
||||
import (
|
||||
"Engine/internal/database"
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
_ "modernc.org/sqlite" // pure-Go SQLite driver, no CGO required
|
||||
)
|
||||
|
||||
// ── Database ──────────────────────────────────────────────────────────────────
|
||||
|
||||
// NewTestDB opens a fresh in-memory SQLite database, runs your schema
|
||||
// migrations, and registers a t.Cleanup to close it when the test ends.
|
||||
//
|
||||
// Each call gets its own isolated database — tests never share state.
|
||||
func NewTestDB(t *testing.T) *sql.DB {
|
||||
t.Helper()
|
||||
|
||||
// "file::memory:?cache=shared" would share across connections;
|
||||
// the plain ":memory:" gives a fully isolated DB per open call.
|
||||
db, err := sql.Open("sqlite", ":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("testutil.NewTestDB: open: %v", err)
|
||||
}
|
||||
|
||||
// SQLite in-memory works best with a single connection.
|
||||
db.SetMaxOpenConns(1)
|
||||
|
||||
if err := database.Migrate(db); err != nil {
|
||||
db.Close()
|
||||
t.Fatalf("testutil.NewTestDB: migrate: %v", err)
|
||||
}
|
||||
|
||||
t.Cleanup(func() { db.Close() })
|
||||
return db
|
||||
}
|
||||
|
||||
// ── HTTP helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
// Do fires an HTTP request directly at handler h and returns the recorded
|
||||
// response. body is marshalled to JSON; pass nil to send no body.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// w := testutil.Do(t, http.HandlerFunc(h.Ingest), http.MethodPost, "/", payload)
|
||||
func Do(t *testing.T, h http.Handler, method, path string, body any) *httptest.ResponseRecorder {
|
||||
t.Helper()
|
||||
|
||||
var r io.Reader
|
||||
if body != nil {
|
||||
b, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
t.Fatalf("testutil.Do: marshal body: %v", err)
|
||||
}
|
||||
r = bytes.NewReader(b)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(method, path, r)
|
||||
if body != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
h.ServeHTTP(w, req)
|
||||
return w
|
||||
}
|
||||
|
||||
// MustJSON marshals v to JSON and returns it as an io.Reader.
|
||||
// Intended for use with http.Client.Post in full-server integration tests.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// resp, _ := client.Post(srv.URL+"/api/v1/budgets", "application/json", testutil.MustJSON(t, payload))
|
||||
func MustJSON(t *testing.T, v any) io.Reader {
|
||||
t.Helper()
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
t.Fatalf("testutil.MustJSON: %v", err)
|
||||
}
|
||||
return bytes.NewReader(b)
|
||||
}
|
||||
|
||||
// ── Assertions ────────────────────────────────────────────────────────────────
|
||||
|
||||
// AssertStatus fails the test if the recorder's status code does not match want.
|
||||
func AssertStatus(t *testing.T, w *httptest.ResponseRecorder, want int) {
|
||||
t.Helper()
|
||||
if w.Code != want {
|
||||
t.Errorf("status: got %d, want %d\n\tbody: %s", w.Code, want, w.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// AssertJSONField decodes the recorder's body as a JSON object and checks that
|
||||
// the top-level key equals the expected string value. Useful for quick smoke
|
||||
// checks without defining a full response struct.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// testutil.AssertJSONField(t, w, "status", "ok")
|
||||
func AssertJSONField(t *testing.T, w *httptest.ResponseRecorder, key, want string) {
|
||||
t.Helper()
|
||||
var m map[string]any
|
||||
if err := json.NewDecoder(w.Body).Decode(&m); err != nil {
|
||||
t.Fatalf("AssertJSONField: decode body: %v", err)
|
||||
}
|
||||
got, ok := m[key]
|
||||
if !ok {
|
||||
t.Errorf("AssertJSONField: key %q not found in response", key)
|
||||
return
|
||||
}
|
||||
if got != want {
|
||||
t.Errorf("AssertJSONField: %q = %q, want %q", key, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// DecodeJSON decodes the recorder's body into dst.
|
||||
// Fails the test immediately if decoding errors.
|
||||
func DecodeJSON(t *testing.T, w *httptest.ResponseRecorder, dst any) {
|
||||
t.Helper()
|
||||
if err := json.NewDecoder(w.Body).Decode(dst); err != nil {
|
||||
t.Fatalf("DecodeJSON: %v\n\tbody: %s", err, w.Body.String())
|
||||
}
|
||||
}
|
||||
334
tests/refrence_test.go
Normal file
334
tests/refrence_test.go
Normal file
@@ -0,0 +1,334 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"Engine/internal/database"
|
||||
"Engine/internal/handler"
|
||||
"Engine/internal/model"
|
||||
"Engine/tests/internal/testutil"
|
||||
)
|
||||
|
||||
// ── wire helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
func newReferenceHandler(t *testing.T) *handler.ReferenceHandler {
|
||||
t.Helper()
|
||||
return handler.NewReferenceHandler(database.NewReferenceRepo(testutil.NewTestDB(t)))
|
||||
}
|
||||
|
||||
// newReferenceServer spins up a real httptest.Server with the production
|
||||
// mux routes. Use this for delete/path-param tests that need {id} routing.
|
||||
func newReferenceServer(t *testing.T) *httptest.Server {
|
||||
t.Helper()
|
||||
h := newReferenceHandler(t)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("POST /api/v1/departments", h.CreateDepartment)
|
||||
mux.HandleFunc("GET /api/v1/departments", h.ListDepartments)
|
||||
mux.HandleFunc("DELETE /api/v1/departments/{id}", h.DeleteDepartment)
|
||||
mux.HandleFunc("POST /api/v1/gl-accounts", h.CreateGLAccount)
|
||||
mux.HandleFunc("GET /api/v1/gl-accounts", h.ListGLAccounts)
|
||||
mux.HandleFunc("DELETE /api/v1/gl-accounts/{id}", h.DeleteGLAccount)
|
||||
|
||||
srv := httptest.NewServer(mux)
|
||||
t.Cleanup(srv.Close)
|
||||
return srv
|
||||
}
|
||||
|
||||
// ── Department: Create ────────────────────────────────────────────────────────
|
||||
|
||||
func TestCreateDepartment_OK(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
|
||||
w := testutil.Do(t, http.HandlerFunc(h.CreateDepartment), http.MethodPost, "/",
|
||||
map[string]any{"code": "ENG", "name": "Engineering", "active": true})
|
||||
|
||||
testutil.AssertStatus(t, w, http.StatusCreated)
|
||||
|
||||
var got database.Department
|
||||
testutil.DecodeJSON(t, w, &got)
|
||||
if got.Code != "ENG" {
|
||||
t.Errorf("code: got %q, want %q", got.Code, "ENG")
|
||||
}
|
||||
if got.ID == 0 {
|
||||
t.Error("expected non-zero ID in response")
|
||||
}
|
||||
if !got.Active {
|
||||
t.Error("expected active=true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateDepartment_DefaultsActiveTrue(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
|
||||
// active field omitted — handler must default it to true
|
||||
w := testutil.Do(t, http.HandlerFunc(h.CreateDepartment), http.MethodPost, "/",
|
||||
map[string]any{"code": "MKT", "name": "Marketing"})
|
||||
|
||||
testutil.AssertStatus(t, w, http.StatusCreated)
|
||||
|
||||
var got database.Department
|
||||
testutil.DecodeJSON(t, w, &got)
|
||||
if !got.Active {
|
||||
t.Error("expected active to default to true when omitted")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateDepartment_MissingCode(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
w := testutil.Do(t, http.HandlerFunc(h.CreateDepartment), http.MethodPost, "/",
|
||||
map[string]any{"name": "Engineering"})
|
||||
testutil.AssertStatus(t, w, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func TestCreateDepartment_MissingName(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
w := testutil.Do(t, http.HandlerFunc(h.CreateDepartment), http.MethodPost, "/",
|
||||
map[string]any{"code": "ENG"})
|
||||
testutil.AssertStatus(t, w, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func TestCreateDepartment_WhitespaceOnly(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
w := testutil.Do(t, http.HandlerFunc(h.CreateDepartment), http.MethodPost, "/",
|
||||
map[string]any{"code": " ", "name": " "})
|
||||
testutil.AssertStatus(t, w, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func TestCreateDepartment_InvalidJSON(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
// nil body → empty body → JSON decode fails → 400
|
||||
w := testutil.Do(t, http.HandlerFunc(h.CreateDepartment), http.MethodPost, "/", nil)
|
||||
testutil.AssertStatus(t, w, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func TestCreateDepartment_Upsert(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
fn := http.HandlerFunc(h.CreateDepartment)
|
||||
|
||||
testutil.Do(t, fn, http.MethodPost, "/", map[string]any{"code": "FIN", "name": "Finance"})
|
||||
|
||||
w := testutil.Do(t, fn, http.MethodPost, "/", map[string]any{"code": "FIN", "name": "Finance Updated"})
|
||||
testutil.AssertStatus(t, w, http.StatusCreated)
|
||||
|
||||
var got database.Department
|
||||
testutil.DecodeJSON(t, w, &got)
|
||||
if got.Name != "Finance Updated" {
|
||||
t.Errorf("upsert name: got %q, want %q", got.Name, "Finance Updated")
|
||||
}
|
||||
}
|
||||
|
||||
// ── Department: List ──────────────────────────────────────────────────────────
|
||||
|
||||
func TestListDepartments_Empty(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
|
||||
w := testutil.Do(t, http.HandlerFunc(h.ListDepartments), http.MethodGet, "/", nil)
|
||||
testutil.AssertStatus(t, w, http.StatusOK)
|
||||
|
||||
var got []database.Department
|
||||
testutil.DecodeJSON(t, w, &got)
|
||||
if len(got) != 0 {
|
||||
t.Errorf("expected empty list, got %d", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestListDepartments_ReturnAll(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
fn := http.HandlerFunc(h.CreateDepartment)
|
||||
|
||||
testutil.Do(t, fn, http.MethodPost, "/", map[string]any{"code": "HR", "name": "Human Resources"})
|
||||
testutil.Do(t, fn, http.MethodPost, "/", map[string]any{"code": "IT", "name": "Information Technology"})
|
||||
testutil.Do(t, fn, http.MethodPost, "/", map[string]any{"code": "OPS", "name": "Operations"})
|
||||
|
||||
w := testutil.Do(t, http.HandlerFunc(h.ListDepartments), http.MethodGet, "/", nil)
|
||||
testutil.AssertStatus(t, w, http.StatusOK)
|
||||
|
||||
var got []database.Department
|
||||
testutil.DecodeJSON(t, w, &got)
|
||||
if len(got) != 3 {
|
||||
t.Errorf("expected 3 departments, got %d", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
// ── Department: Delete ────────────────────────────────────────────────────────
|
||||
|
||||
func TestDeleteDepartment_OK(t *testing.T) {
|
||||
srv := newReferenceServer(t)
|
||||
client := srv.Client()
|
||||
|
||||
resp, err := client.Post(srv.URL+"/api/v1/departments", "application/json",
|
||||
mustJSON(t, map[string]any{"code": "OPS", "name": "Operations"}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var created database.Department
|
||||
json.NewDecoder(resp.Body).Decode(&created)
|
||||
resp.Body.Close()
|
||||
|
||||
req, _ := http.NewRequest(http.MethodDelete, srv.URL+"/api/v1/departments/"+strconv.Itoa(created.ID), nil)
|
||||
resp2, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp2.Body.Close()
|
||||
|
||||
if resp2.StatusCode != http.StatusNoContent && resp2.StatusCode != http.StatusOK {
|
||||
t.Errorf("delete: got %d, want 200 or 204", resp2.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteDepartment_NotFound(t *testing.T) {
|
||||
srv := newReferenceServer(t)
|
||||
|
||||
req, _ := http.NewRequest(http.MethodDelete, srv.URL+"/api/v1/departments/9999", nil)
|
||||
resp, err := srv.Client().Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
// Accept 404 or 204 — adjust to match your handler's behaviour
|
||||
if resp.StatusCode != http.StatusNotFound && resp.StatusCode != http.StatusNoContent {
|
||||
t.Errorf("delete non-existent: got %d, want 404 or 204", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// ── GL Account: Create ────────────────────────────────────────────────────────
|
||||
|
||||
func TestCreateGLAccount_OK(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
|
||||
w := testutil.Do(t, http.HandlerFunc(h.CreateGLAccount), http.MethodPost, "/",
|
||||
map[string]any{"code": "5001", "name": "Travel Expenses", "type": "expense"})
|
||||
|
||||
testutil.AssertStatus(t, w, http.StatusCreated)
|
||||
|
||||
var got database.GLAccount
|
||||
testutil.DecodeJSON(t, w, &got)
|
||||
if got.Code != "5001" {
|
||||
t.Errorf("code: got %q, want %q", got.Code, "5001")
|
||||
}
|
||||
if got.ID == 0 {
|
||||
t.Error("expected non-zero ID")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateGLAccount_MissingCode(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
w := testutil.Do(t, http.HandlerFunc(h.CreateGLAccount), http.MethodPost, "/",
|
||||
map[string]any{"name": "Travel Expenses"})
|
||||
testutil.AssertStatus(t, w, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func TestCreateGLAccount_MissingName(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
w := testutil.Do(t, http.HandlerFunc(h.CreateGLAccount), http.MethodPost, "/",
|
||||
map[string]any{"code": "5001"})
|
||||
testutil.AssertStatus(t, w, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func TestCreateGLAccount_Upsert(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
fn := http.HandlerFunc(h.CreateGLAccount)
|
||||
|
||||
testutil.Do(t, fn, http.MethodPost, "/", map[string]any{"code": "4001", "name": "Revenue"})
|
||||
|
||||
w := testutil.Do(t, fn, http.MethodPost, "/", map[string]any{"code": "4001", "name": "Revenue Updated"})
|
||||
testutil.AssertStatus(t, w, http.StatusCreated)
|
||||
|
||||
var got model.GLAccount
|
||||
testutil.DecodeJSON(t, w, &got)
|
||||
if got.Code != "Revenue Updated" {
|
||||
t.Errorf("upsert name: got %q, want %q", got.Code, "Revenue Updated")
|
||||
}
|
||||
}
|
||||
|
||||
// ── GL Account: List ──────────────────────────────────────────────────────────
|
||||
|
||||
func TestListGLAccounts_Empty(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
w := testutil.Do(t, http.HandlerFunc(h.ListGLAccounts), http.MethodGet, "/", nil)
|
||||
testutil.AssertStatus(t, w, http.StatusOK)
|
||||
|
||||
var got []database.GLAccount
|
||||
testutil.DecodeJSON(t, w, &got)
|
||||
if len(got) != 0 {
|
||||
t.Errorf("expected empty list, got %d", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestListGLAccounts_ReturnAll(t *testing.T) {
|
||||
h := newReferenceHandler(t)
|
||||
fn := http.HandlerFunc(h.CreateGLAccount)
|
||||
|
||||
testutil.Do(t, fn, http.MethodPost, "/", map[string]any{"code": "4001", "name": "Revenue"})
|
||||
testutil.Do(t, fn, http.MethodPost, "/", map[string]any{"code": "5001", "name": "COGS"})
|
||||
|
||||
w := testutil.Do(t, http.HandlerFunc(h.ListGLAccounts), http.MethodGet, "/", nil)
|
||||
testutil.AssertStatus(t, w, http.StatusOK)
|
||||
|
||||
var got []database.GLAccount
|
||||
testutil.DecodeJSON(t, w, &got)
|
||||
if len(got) != 2 {
|
||||
t.Errorf("expected 2 GL accounts, got %d", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
// ── GL Account: Delete ────────────────────────────────────────────────────────
|
||||
|
||||
func TestDeleteGLAccount_OK(t *testing.T) {
|
||||
srv := newReferenceServer(t)
|
||||
client := srv.Client()
|
||||
|
||||
resp, err := client.Post(srv.URL+"/api/v1/gl-accounts", "application/json",
|
||||
mustJSON(t, map[string]any{"code": "6001", "name": "Rent"}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var created database.GLAccount
|
||||
json.NewDecoder(resp.Body).Decode(&created)
|
||||
resp.Body.Close()
|
||||
|
||||
req, _ := http.NewRequest(http.MethodDelete, srv.URL+"/api/v1/gl-accounts/"+strconv.Itoa(created.ID), nil)
|
||||
resp2, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp2.Body.Close()
|
||||
|
||||
if resp2.StatusCode != http.StatusNoContent && resp2.StatusCode != http.StatusOK {
|
||||
t.Errorf("delete: got %d, want 200 or 204", resp2.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteGLAccount_NotFound(t *testing.T) {
|
||||
srv := newReferenceServer(t)
|
||||
|
||||
req, _ := http.NewRequest(http.MethodDelete, srv.URL+"/api/v1/gl-accounts/9999", nil)
|
||||
resp, err := srv.Client().Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusNotFound && resp.StatusCode != http.StatusNoContent {
|
||||
t.Errorf("delete non-existent: got %d, want 404 or 204", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// ── local helpers ─────────────────────────────────────────────────────────────
|
||||
|
||||
func mustJSON(t *testing.T, v any) *bytes.Reader {
|
||||
t.Helper()
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return bytes.NewReader(b)
|
||||
}
|
||||
Reference in New Issue
Block a user