5 Commits

Author SHA1 Message Date
Bryce
b02aec3546 Move sales summaries from admin to pos menu
- Move SSR handler from auto-ap.ssr.admin.sales-summaries to auto-ap.ssr.pos.sales-summaries
- Move route namespace from auto-ap.routes.admin.sales-summaries to auto-ap.routes.pos.sales-summaries
- Update nav to use main-aside-nav with POS breadcrumbs
- Use pos.common date-range-field* filter component
- Remove wrap-admin/wrap-client-redirect-unauthenticated from middleware
- Add Summaries to Sales sidebar menu
2026-05-16 00:13:42 -07:00
Bryce
5a39a0c762 fixes for sales summaries being automatic. 2026-05-15 23:22:38 -07:00
95f12a6072 refactor: remove dead calc-aggregate-totals and unused schema attributes
The 13 sales-summary/total-* attributes were computed and stored but never
read — the only consumer (get-debits) was commented out. Active display code
computes totals on-the-fly from the items list instead.
2026-05-15 23:22:38 -07:00
0e76506c22 consolidate sales summary ledger entry creation into upsert-sales-summary tx
Move journal entry calculation and creation from the reconcile-ledger
background job into the upsert-sales-summary tx function. Now any save
of a sales summary (job recalculation, admin edit wizard, or manual
touch) automatically creates the journal entry if balanced with all
accounts mapped, or retracts it if conditions no longer hold. Eliminates
the need for a separate upsert-sales-summary-ledger call and the
reconcile ledger pass for sales summaries.
2026-05-15 23:22:38 -07:00
baf8cfff97 feat: complete automatic sales summary calculations and ledger posting 2026-05-15 23:22:38 -07:00
7 changed files with 230 additions and 328 deletions

File diff suppressed because one or more lines are too long

View File

@@ -177,9 +177,8 @@
(conj [:paragraph {:color [128 0 0] :size 9} (:warning report)]) (conj [:paragraph {:color [128 0 0] :size 9} (:warning report)])
(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,8 +120,7 @@
(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,9 +201,8 @@
(conj [:paragraph {:color [128 0 0] :size 9} (:warning report)]) (conj [:paragraph {:color [128 0 0] :size 9} (:warning report)])
(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

@@ -1,17 +1,15 @@
(ns auto-ap.ssr.pos.sales-summaries (ns auto-ap.ssr.pos.sales-summaries
(:require (:require
[auto-ap.datomic [auto-ap.datomic
:refer [apply-pagination apply-sort-3 conn merge-query pull-many :refer [apply-pagination apply-sort-3 conn merge-query pull-many
query2]] query2]]
[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]]
@@ -20,35 +18,35 @@
:refer [date-range-field*]] :refer [date-range-field*]]
[auto-ap.ssr.svg :as svg] [auto-ap.ssr.svg :as svg]
[auto-ap.ssr.utils [auto-ap.ssr.utils
:refer [apply-middleware-to-all-handlers clj-date-schema :refer [apply-middleware-to-all-handlers clj-date-schema
default-grid-fields-schema entity-id html-response money default-grid-fields-schema entity-id html-response money
strip temp-id wrap-merge-prior-hx wrap-schema-enforce]] strip temp-id wrap-merge-prior-hx wrap-schema-enforce]]
[auto-ap.time :as atime] [auto-ap.time :as atime]
[bidi.bidi :as bidi] [bidi.bidi :as bidi]
[clj-time.coerce :as c] [clj-time.coerce :as c]
[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= dollars-0?]] [iol-ion.query :refer [dollars=]]
[malli.core :as mc] [malli.core :as mc]
[malli.util :as mut])) [malli.util :as mut]))
(def query-schema (mc/schema (def query-schema (mc/schema
[:maybe [:maybe
(into [:map {:date-range [:date-range :start-date :end-date]} (into [:map {:date-range [:date-range :start-date :end-date]}
[: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]
[:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms" [:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms"
"hx-get" (bidi/path-for ssr-routes/only-routes "hx-get" (bidi/path-for ssr-routes/only-routes
::route/table) ::route/table)
"hx-target" "#entity-table" "hx-target" "#entity-table"
"hx-indicator" "#entity-table"} "hx-indicator" "#entity-table"}
[:fieldset.space-y-6 [:fieldset.space-y-6
(date-range-field* request)]]) (date-range-field* request)]])
@@ -57,14 +55,15 @@
* *
[: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/amount :ledger-mapped/account
:sales-summary-item/category :ledger-mapped/amount
:sales-summary-item/sort-order :sales-summary-item/category
:db/id :sales-summary-item/sort-order
:sales-summary-item/manual?]} :db/id
{:journal-entry/original-entity [:db/id]}]) :sales-summary-item/manual?]
} ])
(defn fetch-ids [db request] (defn fetch-ids [db request]
(let [query-params (:query-params request) (let [query-params (:query-params request)
@@ -73,26 +72,27 @@
(:client-id query-params) (:client-id query-params)
(when (:client-code query-params) (when (:client-code query-params)
[:client/code (:client-code query-params)])) [:client/code (:client-code query-params)]))
query (cond-> {:query {:find [] query (cond-> {:query {:find []
:in '[$ [?client ...]] :in '[$ [?client ...]]
:where '[[?e :sales-summary/client ?client]]} :where '[[?e :sales-summary/client ?client]]}
:args [db valid-clients]} :args [db valid-clients]}
(or (:start-date query-params) (or (:start-date query-params)
(:end-date query-params)) (:end-date query-params))
(merge-query {:query '{:where [[?e :sales-summary/date ?d]]}}) (merge-query {:query '{:where [[?e :sales-summary/date ?d]]}})
(:start-date query-params) (:start-date query-params)
(merge-query {:query '{:in [?start-date] (merge-query {:query '{:in [?start-date]
:where [[(>= ?d ?start-date)]]} :where [[(>= ?d ?start-date)]]}
:args [(-> query-params :start-date c/to-date)]}) :args [(-> query-params :start-date c/to-date)]})
(:end-date query-params) (:end-date query-params)
(merge-query {:query '{:in [?end-date] (merge-query {:query '{:in [?end-date]
: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]]}}))]
(cond->> (query2 query) (cond->> (query2 query)
true (apply-sort-3 query-params) true (apply-sort-3 query-params)
@@ -107,7 +107,7 @@
refunds)) refunds))
(defn fetch-page [request] (defn fetch-page [request]
(let [db (dc/db conn) (let [db (dc/db conn)
{ids-to-retrieve :ids matching-count :count} (fetch-ids db request)] {ids-to-retrieve :ids matching-count :count} (fetch-ids db request)]
[(->> (hydrate-results ids-to-retrieve db request)) [(->> (hydrate-results ids-to-retrieve db request))
@@ -116,29 +116,27 @@
(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 %)))
(map #(:ledger-mapped/amount % 0.0)) (map #(:ledger-mapped/amount % 0.0))
(reduce + 0.0))) (reduce + 0.0)))
(defn total-credits [items] (defn total-credits [items]
(->> items (->> items
(filter #(= :ledger-side/credit (:ledger-mapped/ledger-side %))) (filter #(= :ledger-side/credit (:ledger-mapped/ledger-side %)))
(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
:nav com/main-aside-nav :nav com/main-aside-nav
:fetch-page fetch-page :fetch-page fetch-page
:page-specific-nav filters :page-specific-nav filters
:query-schema query-schema :query-schema query-schema
:row-buttons (fn [_ entity] :row-buttons (fn [_ entity]
[(com/icon-button {:hx-get (bidi/path-for ssr-routes/only-routes [(com/icon-button {:hx-get (bidi/path-for ssr-routes/only-routes
@@ -146,138 +144,84 @@
:db/id (:db/id entity))} :db/id (:db/id entity))}
svg/pencil)]) svg/pencil)])
:oob-render :oob-render
(fn [request] (fn [request]
[(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)]) [(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)])
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes :breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
:company)} :company)}
"POS"] "POS"]
[:a {:href (bidi/path-for ssr-routes/only-routes [:a {:href (bidi/path-for ssr-routes/only-routes
::route/page)} ::route/page)}
"Sales Summaries"]] "Sales Summaries"]]
:title "Sales Summaries" :title "Sales Summaries"
:entity-name "Daily Summary" :entity-name "Daily Summary"
:route ::route/table :route ::route/table
:headers [{:key "client" :headers [{:key "client"
:name "Client" :name "Client"
:sort-key "client" :sort-key "client"
:hide? (fn [args] :hide? (fn [args]
(= (count (:clients args)) 1)) (= (count (:clients args)) 1))
:render #(-> % :sales-summary/client :client/code)} :render #(-> % :sales-summary/client :client/code)}
{:key "date"
:name "Date"
:sort-key "date"
:render #(some-> % :sales-summary/date (atime/unparse-local atime/normal-date))}
{:key "debits" {:key "date"
:name "Debits" :name "Date"
:sort-key "debits" :sort-key "date"
:class "w-72 align-top" :render #(some-> % :sales-summary/date (atime/unparse-local atime/normal-date))}
:render (fn [ss]
(let [items (:sales-summary/items ss)
debit-items (filter #(= :ledger-side/debit (:ledger-mapped/ledger-side %)) (sort-items items))
credit-count (count (filter #(= :ledger-side/credit (:ledger-mapped/ledger-side %)) items))
total-debits (total-debits items)]
[:div.flex.flex-col.h-full
[: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)
[:span.shrink-0 (com/pill {:color :red} "?")])
[:span.shrink-0.font-mono.tabular-nums.text-right.text-gray-900.whitespace-nowrap
(format "$%,.2f" (:ledger-mapped/amount si))]])
(for [_ (range (max 0 (- credit-count (count debit-items))))]
[: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-debits)]]]))}
{:key "credits"
:name "Credits"
:sort-key "credits"
:class "w-72 align-top"
:render (fn [ss]
(let [items (:sales-summary/items ss)
credit-items (filter #(= :ledger-side/credit (:ledger-mapped/ledger-side %)) (sort-items items))
debit-count (count (filter #(= :ledger-side/debit (:ledger-mapped/ledger-side %)) items))
total-credits (total-credits items)]
[:div.flex.flex-col.h-full
[: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)
[:span.shrink-0 (com/pill {:color :red} "?")])
[:span.shrink-0.font-mono.tabular-nums.text-right.text-gray-900.whitespace-nowrap
(format "$%,.2f" (:ledger-mapped/amount si))]])
(for [_ (range (max 0 (- debit-count (count credit-items))))]
[: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" {:key "debits"
:name "Status" :name "debits"
:sort-key "balance" :sort-key "debits"
:class "w-28 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))]
total-debits (total-debits items) [:ul
total-credits (total-credits items) (for [si (sort-items (:sales-summary/items ss))
delta (- total-debits total-credits) :when (= :ledger-side/debit (:ledger-mapped/ledger-side si))]
balanced? (dollars= total-debits total-credits) [:li (:sales-summary-item/category si) ": " (format "$%,.2f" (:ledger-mapped/amount si))
missing-account? (some #(not (:ledger-mapped/account %)) items)] (when-not (:ledger-mapped/account si)
[:div.flex.flex-col.items-center.gap-1.pt-2 [:span.pl-4 (com/pill {:color :red}
(when missing-account? "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"]) [:li (com/pill {:color (if (dollars= total-debits total-credits)
(if balanced? :primary
(when-not missing-account? :red)} "Total: " (format "$%,.2f" total-debits))]]))}
[: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 {:key "credits"
"Balanced"]) :name "credits"
[:div.flex.flex-col.items-center :sort-key "credits"
[:span.font-mono.tabular-nums.text-red-700.font-bold.text-sm :render (fn [ss]
(format "$%,.2f" (Math/abs delta))] (let [total-debits (total-debits (:sales-summary/items ss))
[:span.text-xs.uppercase.tracking-wider.text-red-600.font-medium.mt-0.5 total-credits (total-credits (:sales-summary/items ss))]
(if (> total-debits total-credits) "Debit over" "Credit over")]])]))} [:ul
(for [si (sort-items (:sales-summary/items ss))
{:key "links" :when (= :ledger-side/credit (:ledger-mapped/ledger-side si))]
:name "Links" [:li (:sales-summary-item/category si) ": " (format "$%,.2f" (:ledger-mapped/amount si))
:show-starting "lg" (when-not (:ledger-mapped/account si)
:class "w-8" [:span.pl-4 (com/pill {:color :red}
:render (fn [ss] "missing account")])])
(let [ledger-entry (:journal-entry/original-entity ss)] [:li (com/pill {:color (if (dollars= total-debits total-credits)
(when (seq ledger-entry) :primary
(link-dropdown :red)} "Total: " (format "$%,.2f" total-credits))]]))}]}))
[{: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))
(def edit-schema (def edit-schema
[:map [:map
[:db/id entity-id] [:db/id entity-id]
[:sales-summary/client [:map [:db/id entity-id]]] [:sales-summary/client [:map [:db/id entity-id]]]
[:sales-summary/items [:sales-summary/items
[:vector {:coerce? true} [:vector {:coerce? true}
[:and [:and
[:map [:map
[:db/id [:or entity-id temp-id]] [:db/id [:or entity-id temp-id]]
[:sales-summary-item/category [:string {:decode/string strip}]] [:sales-summary-item/category [:string {:decode/string strip}]]
[:sales-summary-item/manual? {:default false :decode/arbitrary (fn [x] (cond [:sales-summary-item/manual? {:default false :decode/arbitrary (fn [x] (cond
(boolean? x) (boolean? x)
x x
(nil? x) (nil? x)
false false
(str/blank? x) (str/blank? x)
false false
:else :else
@@ -286,10 +230,11 @@
[:credit {:optional true} [:maybe money]] [:credit {:optional true} [:maybe money]]
[:debit {:optional true} [:maybe money]]] [:debit {:optional true} [:maybe money]]]
[:fn {:error/message "Must choose one of credit/debit" [:fn {:error/message "Must choose one of credit/debit"
: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
@@ -304,22 +249,16 @@
(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"}
[:span.text-xs.uppercase.tracking-wider.font-semibold.text-slate-600 (format "$%,.2f" total-debits))
"Total"])
(com/data-grid-cell {:class "text-right"} (com/data-grid-cell {:class "text-right"}
[:span.font-mono.tabular-nums.font-bold.text-slate-900 (format "$%,.2f" total-credits)))))
(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
@@ -331,52 +270,44 @@
: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 "unbalanced-row" (com/data-grid-row {:id "total-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 unbalanced? (when (and
[:span.text-xs.uppercase.tracking-wider.font-semibold.text-red-700 (not (dollars= total-credits total-debits))
"Out of balance"])) (> total-debits total-credits))
(format "$%,.2f" (- total-debits total-credits))))
(com/data-grid-cell {:class "text-right"} (com/data-grid-cell {:class "text-right"}
(when debit-over? (when
[:span.font-mono.tabular-nums.font-bold.text-red-700 (and (not (dollars= total-credits total-debits))
(format "$%,.2f" (- total-debits total-credits))])) (> total-credits total-debits))
(com/data-grid-cell {:class "text-right"} (format "$%,.2f" (- total-credits total-debits)))))))
(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]}]
[:div.flex.flex-col [:div.flex.flex-col
(com/typeahead {:name name (com/typeahead {:name name
:placeholder "Search..." :placeholder "Search..."
:url (hu/url (bidi/path-for ssr-routes/only-routes :account-search) :url (hu/url (bidi/path-for ssr-routes/only-routes :account-search)
{:client-id client-id {:client-id client-id
:purpose "invoice"}) :purpose "invoice"})
:value value :value value
:content-fn (fn [value] :content-fn (fn [value]
(:account/name (d-accounts/clientize (dc/pull (dc/db conn) d-accounts/default-read value) (:account/name (d-accounts/clientize (dc/pull (dc/db conn) d-accounts/default-read value)
client-id)))})]) client-id)))})])
(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 {})}
:class (when manual? (fc/field-value (:new? value)) (hx/htmx-transition-appear ))
"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)}))
@@ -384,51 +315,48 @@
(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 {:class "align-top"} (com/data-grid-cell {}
(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)}
(com/text-input {:placeholder "Category/Explanation" (com/text-input {:placeholder "Category/Explanation"
:name (fc/field-name) :name (fc/field-name)
:value (fc/field-value)})) :value (fc/field-value)}))
(list (list
(com/hidden {:name (fc/field-name) (com/hidden {:name (fc/field-name)
:value (fc/field-value)}) :value (fc/field-value)})
[:span.text-sm.text-gray-700 (fc/field-value (:sales-summary-item/category value))))))
(fc/field-value (:sales-summary-item/category value))])))) (com/data-grid-cell {}
(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 align-top"} (com/data-grid-cell {:class "text-right"}
(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 text-right font-mono tabular-nums" (com/money-input {:class "w-24"
: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)
[:span.font-mono.tabular-nums.text-gray-900.text-sm.whitespace-nowrap (format "$%,.2f" (fc/field-value (:ledger-mapped/amount value))))))
(format "$%,.2f" (fc/field-value (:ledger-mapped/amount value)))]))) (com/data-grid-cell {:class "text-right"}
(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 text-right font-mono tabular-nums" (com/money-input {:class "w-24"
: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)
[:span.font-mono.tabular-nums.text-gray-900.text-sm.whitespace-nowrap (format "$%,.2f" (fc/field-value (:ledger-mapped/amount value))))))
(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))))))
(defrecord MainStep [linear-wizard] (defrecord MainStep [linear-wizard]
@@ -442,45 +370,45 @@
[]) [])
(step-schema [_] (step-schema [_]
(mut/select-keys (mm/form-schema linear-wizard) #{:db/id :sales-summary/items})) (mut/select-keys (mm/form-schema linear-wizard) #{:db/id :sales-summary/items}))
(render-step (render-step
[this {:keys [multi-form-state] :as request}] [this {:keys [multi-form-state] :as request}]
(mm/default-render-step (mm/default-render-step
linear-wizard this linear-wizard this
:head [:div.p-2 "New invoice"] :head [:div.p-2 "New invoice"]
:body (mm/default-step-body :body (mm/default-step-body
{} {}
[:div [:div
(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)}))
(com/data-grid {:headers (com/data-grid {:headers
[(com/data-grid-header {} "Category") [(com/data-grid-header {} "Category")
(com/data-grid-header {} "Account") (com/data-grid-header {} "Account")
(com/data-grid-header {} "Debits") (com/data-grid-header {} "Debits")
(com/data-grid-header {} "Credits") (com/data-grid-header {} "Credits")
(com/data-grid-header {} "")]} (com/data-grid-header {} "")]}
(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
:index (count (fc/field-value)) :index (count (fc/field-value))
: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-[920px] lg:h-[640px]"))) :width-height-class "lg:w-[850px] lg:h-[900px]")))
(defn attach-ledger [i] (defn attach-ledger [i]
(cond-> i (cond-> i
(:credit i) (assoc :ledger-mapped/ledger-side :ledger-side/credit (:credit i) (assoc :ledger-mapped/ledger-side :ledger-side/credit
:ledger-mapped/amount (:credit i)) :ledger-mapped/amount (:credit i))
(:debit i) (assoc :ledger-mapped/ledger-side :ledger-side/debit (:debit i) (assoc :ledger-mapped/ledger-side :ledger-side/debit
@@ -496,8 +424,8 @@
(navigate [this step-key] (navigate [this step-key]
(assoc this :current-step step-key)) (assoc this :current-step step-key))
(get-current-step (get-current-step
[this] [this]
(mm/get-step this :main)) (mm/get-step this :main))
(render-wizard [this {:keys [multi-form-state] :as request}] (render-wizard [this {:keys [multi-form-state] :as request}]
(mm/default-render-wizard (mm/default-render-wizard
this request this request
@@ -509,28 +437,29 @@
(steps [_] (steps [_]
[:main]) [:main])
(get-step [this step-key] (get-step [this step-key]
(let [step-key-result (mc/parse mm/step-key-schema step-key) (let [step-key-result (mc/parse mm/step-key-schema step-key)
[step-key-type step-key] step-key-result] [step-key-type step-key] step-key-result]
(->MainStep this))) (->MainStep this)))
(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
:request request}) :request request})
:headers (cond-> {"hx-trigger" "modalclose" :headers (cond-> {"hx-trigger" "modalclose"
"hx-retarget" (format "#entity-table tr[data-id=\"%d\"]" (:db/id result)) "hx-retarget" (format "#entity-table tr[data-id=\"%d\"]" (:db/id result))
"hx-reswap" "outerHTML"}))))) "hx-reswap" "outerHTML"})))))
@@ -550,8 +479,8 @@
(def key->handler (def key->handler
(apply-middleware-to-all-handlers (apply-middleware-to-all-handlers
(->> (->>
{::route/page (helper/page-route grid-page) {::route/page (helper/page-route grid-page)
::route/table (helper/table-route grid-page) ::route/table (helper/table-route grid-page)
::route/edit-wizard (-> mm/open-wizard-handler ::route/edit-wizard (-> mm/open-wizard-handler
(mm/wrap-wizard edit-wizard) (mm/wrap-wizard edit-wizard)
(mm/wrap-init-multi-form-state initial-edit-wizard-state) (mm/wrap-init-multi-form-state initial-edit-wizard-state)
@@ -569,9 +498,9 @@
(wrap-schema-enforce :query-schema [:map (wrap-schema-enforce :query-schema [:map
[:client-id {:optional true} [:client-id {:optional true}
[:maybe entity-id]]])) [:maybe entity-id]]]))
::route/edit-wizard-submit (-> mm/submit-handler ::route/edit-wizard-submit (-> mm/submit-handler
(mm/wrap-wizard edit-wizard) (mm/wrap-wizard edit-wizard)
(mm/wrap-decode-multi-form-state))}) (mm/wrap-decode-multi-form-state))})
(fn [h] (fn [h]
(-> h (-> h
(wrap-copy-qp-pqp) (wrap-copy-qp-pqp)

View File

@@ -798,34 +798,30 @@
(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-> []
(> client-count 1) (> (count (set (map :client-id (:data pnl-data)))) 1)
(conj (cond-> (into [{:value "Client"}] (conj (into [{:value "Client"}]
(mapcat identity
(for [client client-ids]
(cond-> [{:value (str (-> pnl-data :client-codes (get client)))}]
(> period-count 1)
(into (apply concat (repeat (dec period-count) ["" ""])))))))
show-total? (conj {:value "Total" :bold true :border [:left]})))
(mapcat identity
(for [client (set (map :client-id (:data pnl-data))) ]
(cond-> [{:value (str (-> pnl-data :client-codes (get client)))}]
(> period-count 1)
(into (apply concat (repeat (dec period-count) ["" ""]))))))))
true true
(conj (cond-> (into [{:value "Period Ending"}] (conj (into [{:value "Period Ending"}]
(for [client client-ids (for [client (set (map :client-id (:data pnl-data)))
[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)
period-headers (if (or is-first? period-headers (if (or is-first?
(not (:include-deltas (:args pnl-data)))) (not (:include-deltas (:args pnl-data))))
[{:value period-date}] [{:value period-date}]
[{: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
@@ -894,33 +890,12 @@
: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 [client-ids (set (map :client-id (:data pnl-data))) (let [pnl-datas (for [client-id (set (map :client-id (:data pnl-data)))
client-count (count client-ids) p (:periods (:args pnl-data))]
period-count (count (:periods (:args pnl-data))) (-> pnl-data
show-total? (and (> client-count 1) (= 1 period-count)) (filter-client client-id)
pnl-datas (for [client-id client-ids (filter-period p)))]
p (:periods (:args pnl-data))]
(-> pnl-data
(filter-client client-id)
(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
@@ -937,11 +912,10 @@
(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 (> period-count 1) table (if (and (> (count (:periods (:args pnl-data))) 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,8 +265,7 @@ 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,2 +1,5 @@
#!/bin/bash #!/bin/bash
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 docker run --rm -ti -v ~/dev/integreat/data/solr:/var/solr --network=bridge -p 8983:8983 bryce-solr
#sudo podman container run --user 1000 --privileged --volume /home/notid/dev/integreat/data/solr:/var/solr -p 8983:8983 bryce-solr