9 Commits

Author SHA1 Message Date
de1c154706 Polish sales summary grid and edit dialog
Aligns debit/credit amounts to a right column with tabular-nums;
replaces the in-cell delta and balanced text with chip-style status
indicators; shortens the edit dialog and clarifies its totals/unbalanced
footer rows; gives manual line items a subtle accent so they're
distinguishable from auto-generated rows.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 11:13:32 -07:00
31179278e4 Adds a more actionable view 2026-05-17 08:16:11 -07:00
Bryce
455cec7828 Merge branch 'master' of gitea.story-basking.ts.net:notid/integreat 2026-05-16 07:14:01 -07:00
aeb7891efa Merge branch 'master' of codecommit://integreat 2026-05-16 00:25:37 -07:00
Bryce
1b2e2e4da7 Merge branch 'master' of gitea.story-basking.ts.net:notid/integreat 2026-05-16 00:17:01 -07:00
cc31d8849b Feat/Complete Sales Summaries (#5)
## Summary

Completes the automatic sales summary pipeline end-to-end: the `sales-summaries-v2` job now calculates aggregate totals, preserves manual adjustments, and automatically posts balanced journal entries to the ledger.

## What Changed

**New Datomic transaction function** (`upsert-sales-summary-ledger`)
- Transforms detailed `sales-summary-item`s into aggregated `journal-entry` lines grouped by account and ledger side
- Handles the full upsert: posts a new journal entry for summaries with mapped accounts, or retracts the orphaned entry if items no longer qualify

**Enhanced `sales-summaries-v2` job**
- Calculates and stores 13 aggregate total attributes (card/cash/food-app/gift-card payments, refunds, fees, discounts, tax, tip, returns, unknown, net)
- Preserves manual items (`manual? true`) during recalculation — only auto-calculated items are replaced

**Ledger reconciliation**
- `reconcile-ledger` now queries for sales summaries missing journal entries and repairs them via `:upsert-sales-summary-ledger`, alongside existing invoice and transaction repairs

**Schema**
- Added 13 `total-*` attributes on `sales-summary` (all `db.type/double`, no history)
- Registered the new transaction function in `tx.clj` and `datomic.clj`

**Admin UI cleanup**
- Resolved "clientize" and HTMX `client-id` TODOs in the sales summaries admin page
- `new-summary-item` now correctly passes `client-id` via `hx-vals`
- Removed stale TODO comments and placeholder code

## Files Changed (8)

| File | Purpose |
|------|---------|
| `iol_ion/.../upsert_sales_summary_ledger.clj` | New Datomic tx function |
| `iol_ion/.../tx.clj` | Register new tx function |
| `resources/schema.edn` | 13 new `total-*` attributes |
| `src/.../datomic.clj` | Load new tx namespace |
| `src/.../jobs/sales_summaries.clj` | Aggregate totals + manual item preservation |
| `src/.../ledger.clj` | Sales summary repair in `reconcile-ledger` |
| `src/.../ssr/admin/sales_summaries.clj` | UI TODO cleanup |
| `docs/plans/...plan.md` | Implementation plan document |

Co-authored-by: Bryce <bryce@integreatconsult.com>
Reviewed-on: #5
Co-authored-by: Bryce <bryce@brycecovertoperations.com>
Co-committed-by: Bryce <bryce@brycecovertoperations.com>
2026-05-16 00:16:44 -07:00
Bryce
bd82f555c2 Merge branch 'master' of gitea.story-basking.ts.net:notid/integreat 2026-05-15 19:48:34 -07:00
Bryce
ec5e4e2e1d Merge branch 'master' of gitea.story-basking.ts.net:notid/integreat 2026-05-03 09:37:53 -07:00
Bryce
04bc7cae78 total column 2026-04-09 14:32:39 -07:00
7 changed files with 328 additions and 230 deletions

File diff suppressed because one or more lines are too long

View File

@@ -178,7 +178,8 @@
(conj (conj
(table->pdf report (table->pdf report
(cond-> (into [30 ] (repeat client-count 13)) (cond-> (into [30 ] (repeat client-count 13))
(:include-comparison args) (into (repeat (* 2 client-count) 13)))))) (:include-comparison args) (into (repeat (* 2 client-count) 13))
(and (> client-count 1) (not (:include-comparison args))) (conj 13)))))
output-stream) output-stream)
(.toByteArray output-stream))) (.toByteArray output-stream)))

View File

@@ -120,7 +120,8 @@
(list (list
[:div.text-2xl.font-bold.text-gray-600 (str "Balance Sheet - " (str/join ", " (map :client/name client))) ] [:div.text-2xl.font-bold.text-gray-600 (str "Balance Sheet - " (str/join ", " (map :client/name client))) ]
(rtable/table {:widths (cond-> (into [30 ] (repeat 13 client-count)) (rtable/table {:widths (cond-> (into [30 ] (repeat 13 client-count))
(> (count date) 1) (into (repeat 13 (* 2 client-count (dec (count date)))))) (> (count date) 1) (into (repeat 13 (* 2 client-count (dec (count date)))))
(and (> client-count 1) (= (count date) 1)) (conj 13))
:investigate-url (bidi.bidi/path-for ssr-routes/only-routes ::route/investigate) :investigate-url (bidi.bidi/path-for ssr-routes/only-routes ::route/investigate)
:table report :table report
:warning (not-empty (str/join "\n " (filter not-empty [warning (:warning report)])))} ))))]) :warning (not-empty (str/join "\n " (filter not-empty [warning (:warning report)])))} ))))])
@@ -202,7 +203,8 @@
(conj (conj
(table->pdf report (table->pdf report
(cond-> (into [30 ] (repeat client-count 13)) (cond-> (into [30 ] (repeat client-count 13))
(> (count date) 1) (into (repeat (* 2 client-count (dec (count date))) 13 )))))) (> (count date) 1) (into (repeat (* 2 client-count (dec (count date))) 13 ))
(and (> client-count 1) (= (count date) 1)) (conj 13)))))
output-stream) output-stream)
(.toByteArray output-stream))) (.toByteArray output-stream)))

View File

@@ -6,10 +6,12 @@
[auto-ap.datomic.accounts :as d-accounts] [auto-ap.datomic.accounts :as d-accounts]
[auto-ap.graphql.utils :refer [extract-client-ids]] [auto-ap.graphql.utils :refer [extract-client-ids]]
[auto-ap.query-params :refer [wrap-copy-qp-pqp]] [auto-ap.query-params :refer [wrap-copy-qp-pqp]]
[auto-ap.client-routes :as client-routes]
[auto-ap.routes.pos.sales-summaries :as route] [auto-ap.routes.pos.sales-summaries :as route]
[auto-ap.ssr-routes :as ssr-routes] [auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.common-handlers :refer [add-new-entity-handler]] [auto-ap.ssr.common-handlers :refer [add-new-entity-handler]]
[auto-ap.ssr.components :as com] [auto-ap.ssr.components :as com]
[auto-ap.ssr.components.link-dropdown :refer [link-dropdown]]
[auto-ap.ssr.components.multi-modal :as mm] [auto-ap.ssr.components.multi-modal :as mm]
[auto-ap.ssr.form-cursor :as fc] [auto-ap.ssr.form-cursor :as fc]
[auto-ap.ssr.grid-page-helper :as helper :refer [wrap-apply-sort]] [auto-ap.ssr.grid-page-helper :as helper :refer [wrap-apply-sort]]
@@ -27,7 +29,7 @@
[clojure.string :as str] [clojure.string :as str]
[datomic.api :as dc] [datomic.api :as dc]
[hiccup.util :as hu] [hiccup.util :as hu]
[iol-ion.query :refer [dollars=]] [iol-ion.query :refer [dollars= dollars-0?]]
[malli.core :as mc] [malli.core :as mc]
[malli.util :as mut])) [malli.util :as mut]))
@@ -38,7 +40,7 @@
[:start-date {:optional true} [:start-date {:optional true}
[:maybe clj-date-schema]] [:maybe clj-date-schema]]
[:end-date {:optional true} [:end-date {:optional true}
[:maybe clj-date-schema]] ] [:maybe clj-date-schema]]]
default-grid-fields-schema)])) default-grid-fields-schema)]))
(defn filters [request] (defn filters [request]
@@ -55,15 +57,14 @@
* *
[:sales-summary/date :xform clj-time.coerce/from-date] [:sales-summary/date :xform clj-time.coerce/from-date]
{:sales-summary/client [:client/code :client/name :db/id]} {:sales-summary/client [:client/code :client/name :db/id]}
{:sales-summary/items [{[:ledger-mapped/ledger-side :xform iol-ion.query/ident] [:db/ident] {:sales-summary/items [{[:ledger-mapped/ledger-side :xform iol-ion.query/ident] [:db/ident]}
}
:ledger-mapped/account :ledger-mapped/account
:ledger-mapped/amount :ledger-mapped/amount
:sales-summary-item/category :sales-summary-item/category
:sales-summary-item/sort-order :sales-summary-item/sort-order
:db/id :db/id
:sales-summary-item/manual?] :sales-summary-item/manual?]}
} ]) {:journal-entry/original-entity [:db/id]}])
(defn fetch-ids [db request] (defn fetch-ids [db request]
(let [query-params (:query-params request) (let [query-params (:query-params request)
@@ -90,7 +91,6 @@
:where [[(< ?d ?end-date)]]} :where [[(< ?d ?end-date)]]}
:args [(-> query-params :end-date c/to-date)]}) :args [(-> query-params :end-date c/to-date)]})
true true
(merge-query {:query {:find ['?sort-default '?e] (merge-query {:query {:find ['?sort-default '?e]
:where ['[?e :sales-summary/date ?sort-default]]}}))] :where ['[?e :sales-summary/date ?sort-default]]}}))]
@@ -116,9 +116,6 @@
(defn sort-items [ss] (defn sort-items [ss]
(sort-by (juxt :ledger-mapped/ledger-side :sales-summary-item/sort-order :sales-summary-item/category) ss)) (sort-by (juxt :ledger-mapped/ledger-side :sales-summary-item/sort-order :sales-summary-item/category) ss))
(defn total-debits [items] (defn total-debits [items]
(->> items (->> items
(filter #(= :ledger-side/debit (:ledger-mapped/ledger-side %))) (filter #(= :ledger-side/debit (:ledger-mapped/ledger-side %)))
@@ -131,6 +128,11 @@
(map #(:ledger-mapped/amount % 0.0)) (map #(:ledger-mapped/amount % 0.0))
(reduce + 0.0))) (reduce + 0.0)))
(defn truncate [s max-len]
(if (> (count s) max-len)
(str (subs s 0 (- max-len 3)) "...")
s))
(def grid-page (def grid-page
(helper/build {:id "entity-table" (helper/build {:id "entity-table"
:id-fn :db/id :id-fn :db/id
@@ -163,46 +165,100 @@
(= (count (:clients args)) 1)) (= (count (:clients args)) 1))
:render #(-> % :sales-summary/client :client/code)} :render #(-> % :sales-summary/client :client/code)}
{:key "date" {:key "date"
:name "Date" :name "Date"
:sort-key "date" :sort-key "date"
:render #(some-> % :sales-summary/date (atime/unparse-local atime/normal-date))} :render #(some-> % :sales-summary/date (atime/unparse-local atime/normal-date))}
{:key "debits" {:key "debits"
:name "debits" :name "Debits"
:sort-key "debits" :sort-key "debits"
:class "w-72 align-top"
:render (fn [ss] :render (fn [ss]
(let [total-debits (total-debits (:sales-summary/items ss)) (let [items (:sales-summary/items ss)
total-credits (total-credits (:sales-summary/items ss))] debit-items (filter #(= :ledger-side/debit (:ledger-mapped/ledger-side %)) (sort-items items))
[:ul credit-count (count (filter #(= :ledger-side/credit (:ledger-mapped/ledger-side %)) items))
(for [si (sort-items (:sales-summary/items ss)) total-debits (total-debits items)]
:when (= :ledger-side/debit (:ledger-mapped/ledger-side si))] [:div.flex.flex-col.h-full
[:li (:sales-summary-item/category si) ": " (format "$%,.2f" (:ledger-mapped/amount si)) [:ul.flex-grow
(for [si debit-items]
[:li.flex.items-baseline.gap-2.py-0.5.text-sm.text-gray-700
[:span.flex-1.min-w-0.truncate.text-gray-600
(:sales-summary-item/category si)]
(when-not (:ledger-mapped/account si) (when-not (:ledger-mapped/account si)
[:span.pl-4 (com/pill {:color :red} [:span.shrink-0 (com/pill {:color :red} "?")])
"missing account")])] [:span.shrink-0.font-mono.tabular-nums.text-right.text-gray-900.whitespace-nowrap
) (format "$%,.2f" (:ledger-mapped/amount si))]])
[:li (com/pill {:color (if (dollars= total-debits total-credits) (for [_ (range (max 0 (- credit-count (count debit-items))))]
:primary [:li.py-0.5.text-sm " "])]
:red)} "Total: " (format "$%,.2f" total-debits))]]))} [:div.border-t-2.border-gray-300.mt-1.pt-1.flex.justify-between.items-baseline
[:span.text-xs.uppercase.tracking-wider.font-semibold.text-gray-500 "Total"]
[:span.font-mono.tabular-nums.font-bold.text-gray-900
(format "$%,.2f" total-debits)]]]))}
{:key "credits" {:key "credits"
:name "credits" :name "Credits"
:sort-key "credits" :sort-key "credits"
:class "w-72 align-top"
:render (fn [ss] :render (fn [ss]
(let [total-debits (total-debits (:sales-summary/items ss)) (let [items (:sales-summary/items ss)
total-credits (total-credits (:sales-summary/items ss))] credit-items (filter #(= :ledger-side/credit (:ledger-mapped/ledger-side %)) (sort-items items))
[:ul debit-count (count (filter #(= :ledger-side/debit (:ledger-mapped/ledger-side %)) items))
(for [si (sort-items (:sales-summary/items ss)) total-credits (total-credits items)]
:when (= :ledger-side/credit (:ledger-mapped/ledger-side si))] [:div.flex.flex-col.h-full
[:li (:sales-summary-item/category si) ": " (format "$%,.2f" (:ledger-mapped/amount si)) [:ul.flex-grow
(for [si credit-items]
[:li.flex.items-baseline.gap-2.py-0.5.text-sm.text-gray-700
[:span.flex-1.min-w-0.truncate.text-gray-600
(:sales-summary-item/category si)]
(when-not (:ledger-mapped/account si) (when-not (:ledger-mapped/account si)
[:span.pl-4 (com/pill {:color :red} [:span.shrink-0 (com/pill {:color :red} "?")])
"missing account")])]) [:span.shrink-0.font-mono.tabular-nums.text-right.text-gray-900.whitespace-nowrap
[:li (com/pill {:color (if (dollars= total-debits total-credits) (format "$%,.2f" (:ledger-mapped/amount si))]])
:primary (for [_ (range (max 0 (- debit-count (count credit-items))))]
:red)} "Total: " (format "$%,.2f" total-credits))]]))}]})) [:li.py-0.5.text-sm " "])]
[:div.border-t-2.border-gray-300.mt-1.pt-1.flex.justify-between.items-baseline
[:span.text-xs.uppercase.tracking-wider.font-semibold.text-gray-500 "Total"]
[:span.font-mono.tabular-nums.font-bold.text-gray-900
(format "$%,.2f" total-credits)]]]))}
{:key "balance"
:name "Status"
:sort-key "balance"
:class "w-28 align-top"
:render (fn [ss]
(let [items (:sales-summary/items ss)
total-debits (total-debits items)
total-credits (total-credits items)
delta (- total-debits total-credits)
balanced? (dollars= total-debits total-credits)
missing-account? (some #(not (:ledger-mapped/account %)) items)]
[:div.flex.flex-col.items-center.gap-1.pt-2
(when missing-account?
[:span.inline-block.text-xs.font-semibold.uppercase.tracking-wider.text-amber-800.bg-amber-100.border.border-amber-300.rounded-sm.px-1.5.py-0.5
"Missing acct"])
(if balanced?
(when-not missing-account?
[:span.inline-block.text-xs.font-semibold.uppercase.tracking-wider.text-emerald-800.bg-emerald-100.border.border-emerald-300.rounded-sm.px-1.5.py-0.5
"Balanced"])
[:div.flex.flex-col.items-center
[:span.font-mono.tabular-nums.text-red-700.font-bold.text-sm
(format "$%,.2f" (Math/abs delta))]
[:span.text-xs.uppercase.tracking-wider.text-red-600.font-medium.mt-0.5
(if (> total-debits total-credits) "Debit over" "Credit over")]])]))}
{:key "links"
:name "Links"
:show-starting "lg"
:class "w-8"
:render (fn [ss]
(let [ledger-entry (:journal-entry/original-entity ss)]
(when (seq ledger-entry)
(link-dropdown
[{:link (hu/url (bidi/path-for client-routes/routes :ledger)
{:exact-match-id (:db/id (first ledger-entry))})
:color :yellow
:content "Ledger entry"}]))))}]}))
(def row* (partial helper/row* grid-page)) (def row* (partial helper/row* grid-page))
(def table* (partial helper/table* grid-page)) (def table* (partial helper/table* grid-page))
@@ -233,8 +289,7 @@
:error/path [:credit]} :error/path [:credit]}
(fn [x] (fn [x]
(not (and (:credit x) (not (and (:credit x)
(:debit x))))]]]] ]) (:debit x))))]]]]])
(defn summary-total-row* [request] (defn summary-total-row* [request]
(let [total-credits (-> request (let [total-credits (-> request
@@ -249,16 +304,22 @@
(total-debits))] (total-debits))]
(com/data-grid-row {:id "total-row" (com/data-grid-row {:id "total-row"
:class "bg-slate-50 border-t-2 border-slate-300"
:hx-trigger "change from:closest form target:.amount-field" :hx-trigger "change from:closest form target:.amount-field"
:hx-put (bidi.bidi/path-for ssr-routes/only-routes ::route/expense-account-total) :hx-put (bidi.bidi/path-for ssr-routes/only-routes ::route/expense-account-total)
:hx-target "this" :hx-target "this"
:hx-swap "innerHTML"} :hx-swap "innerHTML"}
(com/data-grid-cell {}) (com/data-grid-cell {})
(com/data-grid-cell {:class "text-right"} [:span.font-bold.text-right "TOTAL"])
(com/data-grid-cell {:class "text-right"} (com/data-grid-cell {:class "text-right"}
(format "$%,.2f" total-debits)) [:span.text-xs.uppercase.tracking-wider.font-semibold.text-slate-600
"Total"])
(com/data-grid-cell {:class "text-right"} (com/data-grid-cell {:class "text-right"}
(format "$%,.2f" total-credits))))) [:span.font-mono.tabular-nums.font-bold.text-slate-900
(format "$%,.2f" total-debits)])
(com/data-grid-cell {:class "text-right"}
[:span.font-mono.tabular-nums.font-bold.text-slate-900
(format "$%,.2f" total-credits)])
(com/data-grid-cell {}))))
(defn unbalanced-row* [request] (defn unbalanced-row* [request]
(let [total-credits (-> request (let [total-credits (-> request
@@ -270,25 +331,31 @@
:multi-form-state :multi-form-state
:step-params :step-params
:sales-summary/items :sales-summary/items
(total-debits))] (total-debits))
unbalanced? (not (dollars= total-credits total-debits))
debit-over? (and unbalanced? (> total-debits total-credits))
credit-over? (and unbalanced? (> total-credits total-debits))]
(com/data-grid-row {:id "total-row" (com/data-grid-row {:id "unbalanced-row"
:class (when unbalanced? "bg-red-50 border-t border-red-200")
:hx-trigger "change from:closest form target:.amount-field" :hx-trigger "change from:closest form target:.amount-field"
:hx-put (bidi.bidi/path-for ssr-routes/only-routes ::route/expense-account-total) :hx-put (bidi.bidi/path-for ssr-routes/only-routes ::route/expense-account-total)
:hx-target "this" :hx-target "this"
:hx-swap "innerHTML"} :hx-swap "innerHTML"}
(com/data-grid-cell {}) (com/data-grid-cell {})
(com/data-grid-cell {:class "text-right"} [:span.font-bold.text-right "UNBALANCED"])
(com/data-grid-cell {:class "text-right"} (com/data-grid-cell {:class "text-right"}
(when (and (when unbalanced?
(not (dollars= total-credits total-debits)) [:span.text-xs.uppercase.tracking-wider.font-semibold.text-red-700
(> total-debits total-credits)) "Out of balance"]))
(format "$%,.2f" (- total-debits total-credits))))
(com/data-grid-cell {:class "text-right"} (com/data-grid-cell {:class "text-right"}
(when (when debit-over?
(and (not (dollars= total-credits total-debits)) [:span.font-mono.tabular-nums.font-bold.text-red-700
(> total-credits total-debits)) (format "$%,.2f" (- total-debits total-credits))]))
(format "$%,.2f" (- total-credits total-debits))))))) (com/data-grid-cell {:class "text-right"}
(when credit-over?
[:span.font-mono.tabular-nums.font-bold.text-red-700
(format "$%,.2f" (- total-credits total-debits))]))
(com/data-grid-cell {}))))
(defn- account-typeahead* (defn- account-typeahead*
[{:keys [name value client-id]}] [{:keys [name value client-id]}]
@@ -306,8 +373,10 @@
(defn sales-summary-item-row* [{:keys [value client-id]}] (defn sales-summary-item-row* [{:keys [value client-id]}]
(let [manual? (fc/field-value (:sales-summary-item/manual? value))] (let [manual? (fc/field-value (:sales-summary-item/manual? value))]
(com/data-grid-row (cond-> {:x-ref "p" (com/data-grid-row (cond-> {:x-ref "p"
:x-data (hx/json {})} :x-data (hx/json {})
(fc/field-value (:new? value)) (hx/htmx-transition-appear )) :class (when manual?
"bg-indigo-50/40 border-l-2 border-indigo-300")}
(fc/field-value (:new? value)) (hx/htmx-transition-appear))
(fc/with-field :db/id (fc/with-field :db/id
(com/hidden {:name (fc/field-name) (com/hidden {:name (fc/field-name)
:value (fc/field-value)})) :value (fc/field-value)}))
@@ -315,7 +384,7 @@
(fc/with-field :sales-summary-item/manual? (fc/with-field :sales-summary-item/manual?
(com/hidden {:name (fc/field-name) (com/hidden {:name (fc/field-name)
:value true}))) :value true})))
(com/data-grid-cell {} (com/data-grid-cell {:class "align-top"}
(fc/with-field :sales-summary-item/category (fc/with-field :sales-summary-item/category
(if manual? (if manual?
(com/validated-field {:errors (fc/field-errors)} (com/validated-field {:errors (fc/field-errors)}
@@ -326,35 +395,38 @@
(list (list
(com/hidden {:name (fc/field-name) (com/hidden {:name (fc/field-name)
:value (fc/field-value)}) :value (fc/field-value)})
(fc/field-value (:sales-summary-item/category value)))))) [:span.text-sm.text-gray-700
(com/data-grid-cell {} (fc/field-value (:sales-summary-item/category value))]))))
(com/data-grid-cell {:class "align-top"}
(fc/with-field :ledger-mapped/account (fc/with-field :ledger-mapped/account
(com/validated-field {:errors (fc/field-errors)} (com/validated-field {:errors (fc/field-errors)}
(account-typeahead* {:value (fc/field-value) (account-typeahead* {:value (fc/field-value)
:client-id client-id :client-id client-id
:name (fc/field-name)})))) :name (fc/field-name)}))))
(com/data-grid-cell {:class "text-right"} (com/data-grid-cell {:class "text-right align-top"}
(if manual? (if manual?
(fc/with-field :debit (fc/with-field :debit
(com/validated-field {:errors (fc/field-errors)} (com/validated-field {:errors (fc/field-errors)}
(com/money-input {:class "w-24" (com/money-input {:class "w-24 text-right font-mono tabular-nums"
:name (fc/field-name) :name (fc/field-name)
:value (fc/field-value)}))) :value (fc/field-value)})))
(when (= (fc/field-value (:ledger-mapped/ledger-side value)) (when (= (fc/field-value (:ledger-mapped/ledger-side value))
:ledger-side/debit) :ledger-side/debit)
(format "$%,.2f" (fc/field-value (:ledger-mapped/amount value)))))) [:span.font-mono.tabular-nums.text-gray-900.text-sm.whitespace-nowrap
(com/data-grid-cell {:class "text-right"} (format "$%,.2f" (fc/field-value (:ledger-mapped/amount value)))])))
(com/data-grid-cell {:class "text-right align-top"}
(if manual? (if manual?
(fc/with-field :credit (fc/with-field :credit
(com/validated-field {:errors (fc/field-errors)} (com/validated-field {:errors (fc/field-errors)}
(com/money-input {:class "w-24" (com/money-input {:class "w-24 text-right font-mono tabular-nums"
:name (fc/field-name) :name (fc/field-name)
:value (fc/field-value)}))) :value (fc/field-value)})))
(when (= (fc/field-value (:ledger-mapped/ledger-side value)) (when (= (fc/field-value (:ledger-mapped/ledger-side value))
:ledger-side/credit) :ledger-side/credit)
(format "$%,.2f" (fc/field-value (:ledger-mapped/amount value)))))) [:span.font-mono.tabular-nums.text-gray-900.text-sm.whitespace-nowrap
(format "$%,.2f" (fc/field-value (:ledger-mapped/amount value)))])))
(com/data-grid-cell {:class "align-top"} (com/data-grid-cell {:class "align-top"}
(when manual? (when manual?
(com/a-icon-button {"@click.prevent.stop" "$refs.p.remove()"} svg/x)))))) (com/a-icon-button {"@click.prevent.stop" "$refs.p.remove()"} svg/x))))))
@@ -392,7 +464,7 @@
(fc/with-field :sales-summary/items (fc/with-field :sales-summary/items
(list (list
(fc/cursor-map #(sales-summary-item-row* {:value % (fc/cursor-map #(sales-summary-item-row* {:value %
:client-id (:db/id (:sales-summary/client (:snapshot multi-form-state))) })) :client-id (:db/id (:sales-summary/client (:snapshot multi-form-state)))}))
(com/data-grid-new-row {:colspan 5 (com/data-grid-new-row {:colspan 5
:hx-get (bidi/path-for ssr-routes/only-routes ::route/new-summary-item) :hx-get (bidi/path-for ssr-routes/only-routes ::route/new-summary-item)
:row-offset 0 :row-offset 0
@@ -400,12 +472,12 @@
:tr-params {:hx-vals (hx/json {:client-id (:db/id (:sales-summary/client (:snapshot multi-form-state)))})}} :tr-params {:hx-vals (hx/json {:client-id (:db/id (:sales-summary/client (:snapshot multi-form-state)))})}}
"New Summary Item"))) "New Summary Item")))
(summary-total-row* request) (summary-total-row* request)
(unbalanced-row* request)) ]) (unbalanced-row* request))])
:footer :footer
(mm/default-step-footer linear-wizard this :validation-route ::route/edit-wizard-navigate) (mm/default-step-footer linear-wizard this :validation-route ::route/edit-wizard-navigate)
:validation-route ::route/edit-wizard-navigate :validation-route ::route/edit-wizard-navigate
:width-height-class "lg:w-[850px] lg:h-[900px]"))) :width-height-class "lg:w-[920px] lg:h-[640px]")))
(defn attach-ledger [i] (defn attach-ledger [i]
(cond-> i (cond-> i
@@ -443,18 +515,17 @@
(form-schema [_] (form-schema [_]
edit-schema) edit-schema)
(submit [this {:keys [multi-form-state request-method identity] :as request}] (submit [this {:keys [multi-form-state request-method identity] :as request}]
(let [result (:snapshot multi-form-state ) (let [result (:snapshot multi-form-state)
transaction [:upsert-sales-summary {:db/id (:db/id result) transaction [:upsert-sales-summary {:db/id (:db/id result)
:sales-summary/items (map :sales-summary/items (map
(fn [i] (fn [i]
(if (:sales-summary-item/manual? i) (if (:sales-summary-item/manual? i)
(attach-ledger i) (attach-ledger i)
{:db/id (:db/id i) {:db/id (:db/id i)
:ledger-mapped/account (:ledger-mapped/account i) :ledger-mapped/account (:ledger-mapped/account i)}))
}))
(:sales-summary/items result))}]] (:sales-summary/items result))}]]
(clojure.pprint/pprint (:sales-summary/items result)) (clojure.pprint/pprint (:sales-summary/items result))
@(dc/transact conn [ transaction]) @(dc/transact conn [transaction])
(html-response (html-response
(row* identity (dc/pull (dc/db conn) default-read (:db/id result)) (row* identity (dc/pull (dc/db conn) default-read (:db/id result))
{:flash? true {:flash? true

View File

@@ -798,20 +798,23 @@
(defn balance-sheet-headers [pnl-data] (defn balance-sheet-headers [pnl-data]
(let [period-count (count (:periods (:args pnl-data)))] (let [period-count (count (:periods (:args pnl-data)))
client-ids (set (map :client-id (:data pnl-data)))
client-count (count client-ids)
show-total? (and (> client-count 1) (= 1 period-count))]
(cond-> [] (cond-> []
(> (count (set (map :client-id (:data pnl-data)))) 1) (> client-count 1)
(conj (into [{:value "Client"}] (conj (cond-> (into [{:value "Client"}]
(mapcat identity (mapcat identity
(for [client (set (map :client-id (:data pnl-data))) ] (for [client client-ids]
(cond-> [{:value (str (-> pnl-data :client-codes (get client)))}] (cond-> [{:value (str (-> pnl-data :client-codes (get client)))}]
(> period-count 1) (> period-count 1)
(into (apply concat (repeat (dec period-count) ["" ""])))))))) (into (apply concat (repeat (dec period-count) ["" ""])))))))
show-total? (conj {:value "Total" :bold true :border [:left]})))
true true
(conj (into [{:value "Period Ending"}] (conj (cond-> (into [{:value "Period Ending"}]
(for [client (set (map :client-id (:data pnl-data))) (for [client client-ids
[index p] (map vector (range) (:periods (:args pnl-data))) [index p] (map vector (range) (:periods (:args pnl-data)))
:let [is-first? (= 0 index) :let [is-first? (= 0 index)
period-date (date->str p) period-date (date->str p)
@@ -821,7 +824,8 @@
[{:value period-date} [{:value period-date}
{:value "+/-"}])] {:value "+/-"}])]
header period-headers] header period-headers]
header)))))) header))
show-total? (conj {:value (date->str (first (:periods (:args pnl-data)))) :border [:left]}))))))
(defn append-deltas [table] (defn append-deltas [table]
(->> table (->> table
@@ -890,12 +894,33 @@
:rows table}))) :rows table})))
) )
(defn add-total-border [rows]
(map (fn [row]
(let [last-idx (dec (count row))]
(map-indexed
(fn [i cell]
(if (= i last-idx)
(let [borders (or (:border cell) [])]
(assoc cell :border (conj borders :left)))
cell))
row)))
rows))
(defn summarize-balance-sheet [pnl-data] (defn summarize-balance-sheet [pnl-data]
(let [pnl-datas (for [client-id (set (map :client-id (:data pnl-data))) (let [client-ids (set (map :client-id (:data pnl-data)))
client-count (count client-ids)
period-count (count (:periods (:args pnl-data)))
show-total? (and (> client-count 1) (= 1 period-count))
pnl-datas (for [client-id client-ids
p (:periods (:args pnl-data))] p (:periods (:args pnl-data))]
(-> pnl-data (-> pnl-data
(filter-client client-id) (filter-client client-id)
(filter-period p)))] (filter-period p)))
total-data (when show-total?
(-> pnl-data
(filter-period (first (:periods (:args pnl-data))))
(assoc :cell-args {:bold true})))
pnl-datas (concat pnl-datas (when total-data [total-data]))]
(let [table (-> [] (let [table (-> []
(into (detail-rows pnl-datas (into (detail-rows pnl-datas
:assets :assets
@@ -912,10 +937,11 @@
(negate #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable})) (negate #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable}))
pnl-datas) pnl-datas)
"Retained Earnings"))) "Retained Earnings")))
table (if (and (> (count (:periods (:args pnl-data))) 1) table (if (and (> period-count 1)
(:include-deltas (:args pnl-data))) (:include-deltas (:args pnl-data)))
(append-deltas table) (append-deltas table)
table)] table)
table (if show-total? (add-total-border table) table)]
{:warning (warning-message pnl-data) {:warning (warning-message pnl-data)
:header (balance-sheet-headers pnl-data) :header (balance-sheet-headers pnl-data)
:rows table})) :rows table}))

View File

@@ -265,7 +265,8 @@ NOTE: Please review the transactions we may have question for you here: https://
[:div.notification.is-warning.is-light [:div.notification.is-warning.is-light
(:warning report)]) (:warning report)])
[rtable/table {:widths (cond-> (into [30 ] (repeat 13 client-count)) [rtable/table {:widths (cond-> (into [30 ] (repeat 13 client-count))
(:include-comparison args) (into (repeat 13 (* 2 client-count)))) (:include-comparison args) (into (repeat 13 (* 2 client-count)))
(and (> client-count 1) (not (:include-comparison args))) (conj 13))
:click-event ::investigate-clicked :click-event ::investigate-clicked
:table report}]])) :table report}]]))

View File

@@ -1,5 +1,2 @@
#!/bin/bash #!/bin/bash
sudo docker run --rm -ti -v ~/dev/integreat/data/solr:/var/solr --network=bridge -p 8983:8983 bryce-solr sudo docker run --rm -ti -v ~/dev/integreat/data/solr:/var/solr --network=bridge -p 8983:8983 679918342773.dkr.ecr.us-east-1.amazonaws.com/integreat-solr
#sudo podman container run --user 1000 --privileged --volume /home/notid/dev/integreat/data/solr:/var/solr -p 8983:8983 bryce-solr