progress on a shared pnl with pdf.

This commit is contained in:
2022-03-18 23:00:57 -07:00
parent 6e41ada061
commit f536d1ac5e
6 changed files with 1646 additions and 172 deletions

View File

@@ -0,0 +1,202 @@
;; This buffer is for Clojure experiments and evaluation.
;; Press C-j to evaluate the last expression.
;; You can also press C-u C-j to evaluate the expression and pretty-print its result.
(init-repl)
(d/pull (d/db auto-ap.datomic/conn) '[* {:invoice/client [:client/code] :invoice/vendor [:vendor/name]}] 17592253143914)
(entity-history 17592253143914 )
(entity-history 13194206632809)
(d/pull (d/db auto-ap.datomic/conn ) '[*] [:client/code "AFH"])
(auto-ap.parse/best-match (auto-ap.datomic.clients/get-all) "NACHMARKET" 0.5)
(println "hi")
(user/init-repl)
(def z (get-in (get-profit-and-loss {:id {:user/role "admin"}}
{:client_id 17592244691558
:periods [{:start #inst "2021-01-01"
:end #inst "2021-12-31"}]}
nil)
[:periods 0 :accounts]))
(filter #(not (:numeric_code %)) z)
(d/q '[:find (pull ?je [*])
:in $
:where [?je :journal-entry/client [:client/code "NGWH"]]
[?je :journal-entry/date ?d]
[?je :journal-entry/line-items ?jel]
(not [?jel :journal-entry-line/account])]
(d/db conn))
(d/pull (d/db conn) '[*] 17592253348380)
(user/entity-history 17592253348380)
(d/pull (d/db conn) '[*] 13194206837275)
[(d/pull (d/db conn) '[*] 17592249726033)
(d/pull (d/db conn) '[*] 17592248612376)]
[(user/entity-history-with-revert 17592249726033)
(user/entity-history-with-revert 17592248612376)]
(d/pull (d/db conn) '[*] 17592186046415)
(user/entity-history 13194203214928)
;; => [[13194203214928 :db/txInstant #inst "2021-12-01T03:04:17.049-00:00"]
;; [13194203214928 :audit/user ":admin-Yodlee import"]]
(user/entity-history 13194202101271)
;; => [[13194202101271 :audit/user ":admin-Yodlee import"]
;; [13194202101271 :db/txInstant #inst "2021-11-25T16:32:46.542-00:00"]]
(user/entity-history 13194203197770)
;; => [[13194203197770 :db/txInstant #inst "2021-12-01T02:07:33.416-00:00"]]
(->> (d/db conn)
(d/q '[:find ?ba (count ?d)
:in $
:where [?ba :bank-account/intuit-bank-account ?d]])
#_(filter (fn [[_ x]]
(> x 1))))
(d/pull (d/db auto-ap.datomic/conn) '[* {:transaction/bank-account [*]}] 17592204620282);; => {:transaction/bank-account
;; {:bank-account/sort-order 5,
;; :bank-account/include-in-reports true,
;; :bank-account/numeric-code 11307,
;; :bank-account/yodlee-account-id 16428403,
;; :bank-account/number "9392",
;; :bank-account/code "HIM-5",
;; :bank-account/current-balance 2063.2899999998335,
;; :bank-account/external-id 5,
;; :bank-account/name "HIM Citi Visa Costco 9392",
;; :db/id 17592186046421,
;; :bank-account/visible true,
;; :bank-account/type #:db{:id 17592186045425},
;; :bank-account/bank-name "Citi Visa CC",
;; :bank-account/original-id "55-5"},
;; :transaction/date #inst "2019-08-03T07:00:00.000-00:00",
;; :transaction/type "PURCHASE",
;; :transaction/client #:db{:id 17592186046415},
;; :transaction/status "POSTED",
;; :transaction/account-id 16428403,
;; :db/id 17592204620282,
;; :transaction/id
;; "192dc451434cb0fc8698fae563618807fddfbc16e2aae8adc488d3dbb1cb5016",
;; :transaction/description-original "TAO TAO CAFE SUNNYVALE CA",
;; :transaction/approval-status #:db{:id 17592231963878},
;; :transaction/amount -40.5,
;; :transaction/description-simple "TAO TAO CAFE SUNNYVALE CA"}
;; => {:transaction/bank-account #:db{:id 17592186046421},
;; :transaction/date #inst "2019-08-03T07:00:00.000-00:00",
;; :transaction/type "PURCHASE",
;; :transaction/client #:db{:id 17592186046415},
;; :transaction/status "POSTED",
;; :transaction/account-id 16428403,
;; :db/id 17592204620282,
;; :transaction/id
;; "192dc451434cb0fc8698fae563618807fddfbc16e2aae8adc488d3dbb1cb5016",
;; :transaction/description-original "TAO TAO CAFE SUNNYVALE CA",
;; :transaction/approval-status #:db{:id 17592231963878},
;; :transaction/amount -40.5,
;; :transaction/description-simple "TAO TAO CAFE SUNNYVALE CA"}
(d/pull (d/db conn) '[*] 17592233127577)
;; => {:transaction/bank-account #:db{:id 17592186046421},
;; :transaction/date #inst "2020-07-29T07:00:00.000-00:00",
;; :transaction/type "PURCHASE",
;; :transaction/client #:db{:id 17592186046415},
;; :transaction/status "POSTED",
;; :transaction/account-id 16428403,
;; :db/id 17592233127577,
;; :transaction/id
;; "217ce419afa1daa217ca118d10ddcfa11e757788b48990bf43d92faf9250e531",
;; :transaction/description-original "TAO TAO CAFE SUNNYVALE CA",
;; :transaction/approval-status #:db{:id 17592231963878},
;; :transaction/amount -49.7,
;; :transaction/description-simple "tao tao cafe"}
(user/init-repl)
(d/pull (d/db conn) '[* {:transaction/approval-status [:db/ident]}] 17592251272764);; => {:transaction/bank-account #:db{:id 17592234230556},
;; :transaction/date #inst "2021-12-06T08:00:00.000-00:00",
;; :transaction/check-number 387,
;; :transaction/client #:db{:id 17592234230520},
;; :transaction/status "POSTED",
;; :db/id 17592251272764,
;; :transaction/id
;; "c15d3d1cad77b4a79510877f464b909e9ec9c8c4bfe9885008666526d6ca9885",
;; :transaction/description-original "Check Paid CHECK 387",
;; :transaction/approval-status
;; #:db{:ident :transaction-approval-status/suppressed},
;; :transaction/amount -12411.0,
;; :transaction/raw-id
;; "2021-12-06T00:00:00.000-08:00-17592234230556-Check Paid CHECK 387--12411.0-0-17592234230520"}
(def z (auto-ap.yodlee.core/get-specific-transactions-with-date "16428403" "2021-01-01" "2021-12-31" (auto-ap.yodlee.core/get-auth-header)))
(count z)
(csv/write-csv *out*
(->> z
(sort-by :date)
(map (fn [z]
(update z :date #(auto-ap.time/unparse (auto-ap.time/parse % auto-ap.time/iso-date) auto-ap.time/normal-date))))
(map (fn [z]
[(:date z) (:original (:description z)) (:original (:description z)) "" (if (= "DEBIT" (:baseType z))
(- (:amount (:amount z)))
(:amount (:amount z)))]
))
)
:separator \tab
)
(count z)
(def z-p (d/q '[:find (pull ?t [:transaction/date :transaction/description-original :transaction/amount])
:in $ ?ba
:where [?t :transaction/bank-account ?ba]
[?t :transaction/date ?d]
[(>= ?d #inst "2021-01-01T08:00:00")]
[(< ?d #inst "2022-01-01T08:00:00")]
(not [?t :transaction/approval-status :transaction-approval-status/suppressed])]
(d/db auto-ap.datomic/conn)
[:bank-account/code "HIM-5"]))
(csv/write-csv *out*
(->> z-p
(map first)
(sort-by :transaction/date)
(map (fn [z]
(update z :transaction/date #(auto-ap.time/unparse-local (clj-time.coerce/to-date-time %) auto-ap.time/normal-date))))
(map (fn [z]
[(:transaction/date z) (:transaction/description-original z) (:transaction/description-original z) "" (:transaction/amount z)]
))
)
:separator \tab
)
(init-repl)

View File

@@ -0,0 +1,709 @@
(ns debug-sccb-transactions
(:require [datomic.api :as d]
[auto-ap.datomic :refer [conn]]
[auto-ap.utils :refer [dollars=]]
[auto-ap.time :as atime :refer [iso-date]]
[clj-time.core :as t]
[clj-time.coerce :as coerce]
[clj-time.periodic :as periodic]
[clojure.data.csv :as csv]
[auto-ap.yodlee.core :as y]
[auto-ap.yodlee.core2 :as y2]))
(d/pull (d/db conn) '[*] [:bank-account/code "SCCB-9128CB"])
(def trans (y/get-specific-transactions-with-date 27995674 "2021-01-01" "2021-12-31" (y/get-auth-header)))
(user/init-repl)
[(->> trans
(sort-by :date)
(map (fn [t]
(if (= "DEBIT" (:baseType t))
(- (:amount (:amount t)))
(:amount (:amount t)))))
(reduce + 0.0))
(:amount (:runningBalance (first trans)))
(:amount (:runningBalance (last trans)))
(- (:amount (:runningBalance (last trans)))
(:amount (:runningBalance (first trans))))]
(defn y-tx->$ [b]
(if (= "DEBIT" (:baseType b))
(- (:amount (:amount b)))
(:amount (:amount b))))
(do
(doseq [bank-account #_ (d/q '[:find [?yai ...]
:in $
:where [_ :bank-account/yodlee-account-id ?yai]]
(d/db conn))
:when (not= 0 bank-account)]
(try
(println
[bank-account (->> (y/get-specific-transactions-with-date bank-account "2021-01-01" "2021-12-31" (y/get-auth-header))
(reverse)
(filter :runningBalance)
(partition 2 1)
(map (fn [[a b]]
(try
[(:amount (:runningBalance a))
(- (:amount (:runningBalance b))
(if (= "DEBIT" (:baseType b))
(- (:amount (:amount b)))
(:amount (:amount b))))
]
(catch Exception e
(println a b)
(println e)
(throw e)
))
))
(filter (fn [[a b]]
(not (dollars= a b))))
count
)])
(catch Exception e
(println "please retry" bank-account)))
)
(println "done"))
(take 10 trans)
(first trans)
(last trans)
(csv/write-csv *out*
(->> trans
(sort-by :date)
(map (fn [z]
(update z :date #(auto-ap.time/unparse (auto-ap.time/parse % auto-ap.time/iso-date) auto-ap.time/normal-date))))
(map (fn [z]
[(:date z) (:original (:description z)) (if (= "DEBIT" (:baseType z))
(- (:amount (:amount z)))
(:amount (:amount z)))]
))
)
:separator \tab
)
(def deep-dive (y/get-specific-transactions-with-date 27995674 "2021-12-03" "2021-12-03" (y/get-auth-header)))
(csv/write-csv *out*
(->> (->> (y/get-specific-transactions-with-date 27995674 "2021-01-01" "2021-12-31" (y/get-auth-header))
(group-by (fn [d]
((juxt :description
:amount
:date
:baseType
:type
:isManual
:merchantType
:status
:CONTAINER
:runningBalance
:subType
:isPhysical
:accountId
:postDate) d)))
(map (fn [[k v]]
(first (sort-by :id v)))))
(sort-by :date)
(map (fn [z]
(update z :date #(auto-ap.time/unparse (auto-ap.time/parse % auto-ap.time/iso-date) auto-ap.time/normal-date))))
(map (fn [z]
[(:date z) (:original (:description z)) (if (= "DEBIT" (:baseType z))
(- (:amount (:amount z)))
(:amount (:amount z)))]
))
)
:separator \tab
)
(->> (y/get-specific-transactions-with-date 27995674 "2021-12-30" "2021-12-30" (y/get-auth-header))
(group-by (fn [d]
((juxt :description
:amount
:date
:baseType
:type
:isManual
:merchantType
:status
:CONTAINER
:runningBalance
:subType
:isPhysical
:accountId
:postDate) d)))
(map (fn [[k v]]
[k (count v)]
#_(clojure.data/diff a b))))
(y2/get-accounts "DEMO2")
(->> (y2/get-specific-transactions "DEMO2" 15565801)
(group-by (fn [d]
((juxt :description
:amount
:date
:baseType
:type
:isManual
:merchantType
:status
:CONTAINER
:runningBalance
:subType
:isPhysical
:accountId
:postDate) d)))
(map (fn [[k v]]
[k (count v)]
#_(clojure.data/diff a b)))
count)
(csv/write-csv *out*
(->> (y2/get-specific-transactions "DEMO2" 15565801)
(sort-by :date)
(map (fn [z]
(update z :date #(auto-ap.time/unparse (auto-ap.time/parse % auto-ap.time/iso-date) auto-ap.time/normal-date))))
(map (fn [z]
[(:date z) (:original (:description z)) (if (= "DEBIT" (:baseType z))
(- (:amount (:amount z)))
(:amount (:amount z)))]
)))
:separator \tab)
(def bad-as [24265567
16422563
27327396
16279665
16279668
16279669
16279670
16279671
24230113
16279664
16421944
24287315
27723398
27723397
16422358
18685498
16422285
24370905
18911886
27995674
19935858
16422708])
(def all-as [
[18409209 0]
[18409210 0]
[27040638 0]
[18409211 0]
[27040639 0]
[27040640 0]
[27398018 0]
[24249175 0]
[16551129 0]
[24781151 0]
[24265567 2433]
[25829735 0]
[25976674 0]
[25829733 0]
[25829734 0]
[26403171 0]
[26403172 0]
[26403169 0]
[26403170 0]
[19679001 0]
[20772618 0]
[26403173 0]
[27509142 0]
[19679000 0]
[18937120 0]
[22730534 0]
[25640840 0]
[16551102 0]
[20186867 0]
[27784044 0]
[26216350 0]
[27784043 0]
[27784042 0]
[21961051 0]
[16422563 48]
[22981959 0]
[16422564 0]
[18356995 0]
[16422562 0]
[27040641 0]
[27040642 0]
[27040643 0]
[26997692 0]
[26997691 0]
[26793947 0]
[27327396 64]
[16279665 83]
[16279666 0]
[26988499 0]
[16279667 0]
[16279668 12]
[16279669 16]
[16279670 55]
[16279671 4]
[16279672 0]
[24230113 80]
[25624533 0]
[25624532 0]
[16279663 0]
[16279664 441]
[26991555 0]
[27798450 0]
[27798451 0]
[26607050 0]
[16422491 0]
[16422492 0]
[16422495 0]
[16422493 0]
[27847951 0]
[16422494 0]
[25646832 0]
[25646831 0]
[16421963 0]
[16422473 0]
[26920161 0]
[16422471 0]
[16422472 0]
[16421945 0]
[16422463 0]
[16422462 0]
[24591293 0]
[16421943 0]
[16421944 8]
[24566177 0]
[24434091 0]
[24434092 0]
[24104846 0]
[24104845 0]
[24104844 0]
[24411026 0]
[24104839 0]
[24104838 0]
[21754236 0]
[27576149 0]
[18099105 0]
[24499586 0]
[18937304 0]
[18937305 0]
[25308479 0]
[25308480 0]
[24104849 0]
[23662442 0]
[25308484 0]
[25308481 0]
[16876498 0]
[24265569 0]
[16876499 0]
[24265570 0]
[24287315 317]
[26983548 0]
[24287316 0]
[27723399 0]
[27723398 21]
[27723397 110]
[24287319 0]
[27576450 0]
[24287317 0]
[27576451 0]
[24287318 0]
[26983549 0]
[27576452 0]
[26794100 0]
[24287326 0]
[16422358 13]
[17303033 0]
[26942060 0]
[24776257 0]
[16876551 0]
[22883931 0]
[17970207 0]
[27361904 0]
[27361903 0]
[19941347 0]
[27345526 0]
[16417200 0]
[16428453 0]
[19756531 0]
[16428443 0]
[18685498 18]
[27465416 0]
[27465415 0]
[27465417 0]
[16422296 0]
[27847900 0]
[27847902 0]
[16422285 11]
[25350341 0]
[16428403 0]
[16551286 0]
[27282619 0]
[27282620 0]
[19942033 0]
[19942034 0]
[26220018 0]
[27780614 0]
[27793421 0]
[17303150 0]
[27793422 0]
[16428372 0]
[24370905 450]
[16428375 0]
[18911886 44]
[16428373 0]
[16428374 0]
[18911888 0]
[27995676 0]
[27995675 0]
[27995674 861]
[19942029 0]
[17408629 0]
[19942030 0]
[22219439 0]
[19942031 0]
[19942032 0]
[25543675 0]
[19935858 606]
[16422707 0]
[16422708 320]
[27116551 0]
[16428317 0]
[16428318 0]
[24510110 0]
[27847749 0]
[654654654 0]
[21489791 0]
[21489790 0]
[23139427 0]
[16422643 0]
[16422644 0]
[16422642 0]
[16428279 0]
[25247822 0]
[18409174 0]
[16422645 0]
[18948276 0]
[18948275 0]
])
(filter (fn [[_ x]] (not= 0 x)) all-as)
(->> (d/q '[:find [(pull ?ba [:bank-account/name :bank-account/code]) ...]
:in $ [?yai ...]
:where [?ba :bank-account/yodlee-account-id ?yai]]
(d/db conn)
)
(map (juxt :bank-account/name :bank-account/code)))
(def trans (y/get-specific-transactions-with-date 27995674 "2021-01-01" "2021-12-31" (y/get-auth-header)))
(def starting (:amount (:runningBalance (last trans))))
(def dates (sort (set (map :date trans))))
(defn find-mistakes [trans-set]
(let [sequences (->> trans-set
(filter :runningBalance)
(map (fn [index t]
{:index index
:id (:id t)
:starting (- (:amount (:runningBalance t))
(if (= "DEBIT" (:baseType t))
(- (:amount (:amount t)))
(:amount (:amount t))))
:amount (if (= "DEBIT" (:baseType t))
(- (:amount (:amount t)))
(:amount (:amount t)))
:after (:amount (:runningBalance t))
:original t}
) (range)))
graph (reduce
(fn [graph s]
(assoc graph s
(filter (fn [s2]
(dollars= (:starting s2)
(:after s))
) sequences)
)
)
{}
sequences)
mistakes (filter
(fn [[k v]]
(> (count v) 1)
)
graph)]
(->> mistakes
(map second)
(map (fn [duplicates]
(map :original duplicates)))
)))
(csv/write-csv *out*
(for [a bad-as
:let [{:bank-account/keys [name code]} (first (d/q '[:find [(pull ?ba [:bank-account/name :bank-account/code]) ...]
:in $ ?yai
:where [?ba :bank-account/yodlee-account-id ?yai]]
(d/db conn)
a))
transactions (y/get-specific-transactions-with-date a "2021-01-01" "2021-12-31" (y/get-auth-header))]
mistakes (->> transactions
(group-by :date)
vals
(mapcat find-mistakes))
]
[code
name
(:date mistakes)
(:amount (:amount mistakes))])
:separator \tab)
(def mistake-set
(doall
(for [a bad-as
:let [{:bank-account/keys [name code]} (first (d/q '[:find [(pull ?ba [:bank-account/name :bank-account/code]) ...]
:in $ ?yai
:where [?ba :bank-account/yodlee-account-id ?yai]]
(d/db conn)
a))
transactions (y/get-specific-transactions-with-date a "2021-01-01" "2021-12-31" (y/get-auth-header))]
]
[code
(->> transactions
(group-by :date)
vals
(map find-mistakes)
(filter seq))])))
(user/init-repl)
mistake-set
(csv/write-csv *out*
(mapcat identity
(for [[[client mistake-set] i] (map vector mistake-set (range))
[mistake-set i2] (map vector mistake-set (range))
[mistake-set i3] (map vector mistake-set (range))
:let [already-fixed? (= (->> mistake-set
(map #(d/pull (d/db conn)
[:db/id {:transaction/approval-status [:db/ident]}]
[:transaction/id
(digest/sha-256 (str(:id %)))])
)
(filter :db/id)
(filter #(and (not= :transaction-approval-status/suppressed (:db/ident (:transaction/approval-status %)) )
(not= :transaction-approval-status/exclude-from-ledger (:db/ident (:transaction/approval-status %)))))
count)
1)]]
(mapv
(fn [m]
(let [iol-transaction (d/pull (d/db conn)
[:db/id {:transaction/approval-status [:db/ident]
:transaction/bank-account [:bank-account/code]}]
[:transaction/id
(digest/sha-256 (str(:id m)))])]
[(str i "-" i2 "-" i3)
(or (:bank-account/code (:transaction/bank-account iol-transaction))
"not found")
(:date m)
(:amount (:amount m))
(:id m)
(or (:db/id iol-transaction) "not found")
(or (some-> (:db/ident (:transaction/approval-status iol-transaction))
name)
"not found")
already-fixed?
]
))
mistake-set)))
:separator \tab)
(->> trans
(reverse)
(filter :runningBalance)
(partition 2 1)
(map (fn [[a b]]
(try
[
(- starting
(:amount (:runningBalance a)))
(- starting
(:amount (:runningBalance b))
(if (= "DEBIT" (:baseType b))
(- (:amount (:amount b)))
(:amount (:amount b))))
]
(catch Exception e
(println a b)
(println e)
(throw e)
))
))
(filter (fn [[a b]]
(not (dollars= a b))))
)
(csv/write-csv *out*
(->> trans
(reverse)
(map (fn [t]
[(:date t)
(:amount (:runningBalance t))
(if (= "DEBIT" (:baseType t))
(- (:amount (:amount t)))
(:amount (:amount t)))]
)))
:separator \tab)
(def ts (auto-ap.intuit.core/get-transactions "2022-02-01" "2022-02-05" "BCFM - Heritage Main 7362 ---BCFM-H7362"))
(count ts)
(defn check-transactions [db cc bac ba yba]
(let [all-transactions (d/q '[:find ?d ?a ?tx
:in $ ?ba
:where [?tx :transaction/bank-account ?ba]
[?tx :transaction/date ?d]
[(>= ?d #inst "2021-01-01T00:00:00-08:00")]
[(<= ?d #inst "2021-12-31T00:00:00-08:00")]
(not [?tx :transaction/approval-status :transaction-approval-status/suppressed])
(not [?tx :transaction/approval-status :transaction-approval-status/excluded])
[?tx :transaction/amount ?a]]
db ba)
not-excluded (fn [t]
(not (y/known-bad-yodlee-ids (:id t))))
all-yodlee-transactions (->> (y/get-specific-transactions-with-date yba "2021-01-01" "2021-12-31" (y/get-auth-header))
(filter not-excluded))
transactions-by-date (->> all-transactions
(group-by first)
(map (fn [[k v]]
[(atime/unparse-local (coerce/to-date-time k) iso-date) v]))
(into {}))
yodlee-transactions-by-date (->> all-yodlee-transactions
(group-by :date)
(into {}))
yodlee-last-updated (-> (y/get-account yba (y/get-auth-header)) first :dataset first :lastUpdated)
]
(for [d (->>
(periodic/periodic-seq (coerce/to-date-time #inst "2021-01-01T00:00:00-08:00") (t/days 1))
(map (fn [d]
(atime/unparse d iso-date)))
(take 365))
:let [txs (transactions-by-date d)
y-txes (yodlee-transactions-by-date d)
y-total (reduce + 0.0 (map y-tx->$ y-txes))
t-total (reduce + 0.0 (map (fn [[_ a]
]
a)
txs))]]
(into
[cc bac
yba d
(count txs ) (count y-txes)
t-total y-total]
(cond
(and
(= (count txs ) (count y-txes) 0)
(>= (.compareTo d yodlee-last-updated) 0))
["Match but yodlee feed stopped"
(format "Date %s is after yodlee feed ended (%s)", d yodlee-last-updated)]
(and
(= (count txs ) (count y-txes))
(dollars= t-total y-total))
["Perfect Match"
""]
(>= (.compareTo d yodlee-last-updated) 0)
["Mismatched because yodlee feed stopped"
(format "Date %s is after yodlee feed ended (%s)", d yodlee-last-updated)]
(not= (count txs ) (count y-txes))
["Mismatched number of transactions"
""]
(not (dollars= t-total y-total))
["Transactions don't add up"
""]
:else
["Mismatch"
"Mismatch, unknown cause"]
))
)))
(do ;;find iol duplicates
#_(check-transactions (d/db auto-ap.datomic/conn) 17592188489475 18911886 )
(csv/write-csv *out*
(let [db (d/db auto-ap.datomic/conn)]
(for [[ba yba bac cc] (d/q '[:find ?ba ?yba ?bac ?cc
:in $
:where [?ba :bank-account/yodlee-account-id ?yba]
[(not= ?yba 0)]
[?ba :bank-account/code ?bac]
[?c :client/bank-accounts ?ba]
[?c :client/code ?cc]]
db
)
row (try (check-transactions db cc bac ba yba)
(catch Exception _
[]))]
row))
:separator \tab)
)

View File

@@ -119,27 +119,7 @@
:phone {:type 'String}}} :phone {:type 'String}}}
:balance_sheet_account :address
{:fields {:id {:type 'String}
:amount {:type 'String}
:location {:type 'String}
:client_id {:type :id}
:count {:type 'Int}
:numeric_code {:type 'Int}
:account_type {:type :account_type}
:name {:type 'String}}}
:balance_sheet
{:fields {:balance_sheet_accounts {:type '(list :balance_sheet_account)}
:comparable_balance_sheet_accounts {:type '(list :balance_sheet_account)}}}
:profit_and_loss_report_period
{:fields {:accounts {:type '(list :balance_sheet_account)}}}
:profit_and_loss_report
{:fields {:periods {:type '(list :profit_and_loss_report_period)}}}
:address
{:fields {:street1 {:type 'String} {:fields {:street1 {:type 'String}
:street2 {:type 'String} :street2 {:type 'String}
:city {:type 'String} :city {:type 'String}
@@ -201,26 +181,7 @@
:sent {:type 'String} :sent {:type 'String}
:vendor {:type :vendor}}} :vendor {:type :vendor}}}
:journal_entry_line
{:fields {:id {:type :id}
:account {:type :account}
:location {:type 'String}
:debit {:type 'String}
:credit {:type 'String}
:running_balance {:type :money}}}
:journal_entry
{:fields {:id {:type :id}
:source {:type 'String}
:external_id {:type 'String}
:original_entity {:type :id}
:amount {:type 'String}
:note {:type 'String}
:cleared_against {:type 'String}
:client {:type :client}
:vendor {:type :vendor}
:alternate_description {:type 'String}
:date {:type 'String}
:line_items {:type '(list :journal_entry_line)}}}
:order_line_item :order_line_item
@@ -251,10 +212,6 @@
:date {:type 'String} :date {:type 'String}
:charges {:type '(list :charge)} :charges {:type '(list :charge)}
:line_items {:type '(list :order_line_item)}}} :line_items {:type '(list :order_line_item)}}}
:yodlee_merchant {:fields {:id {:type :id} :yodlee_merchant {:fields {:id {:type :id}
:yodlee_id {:type 'String} :yodlee_id {:type 'String}
:name {:type 'String}}} :name {:type 'String}}}
@@ -322,11 +279,7 @@
:start {:type 'Int} :start {:type 'Int}
:end {:type 'Int}}} :end {:type 'Int}}}
:ledger_page {:fields {:journal_entries {:type '(list :journal_entry)}
:count {:type 'Int}
:total {:type 'Int}
:start {:type 'Int}
:end {:type 'Int}}}
:sales_order_page {:fields {:sales_orders {:type '(list :sales_order)} :sales_order_page {:fields {:sales_orders {:type '(list :sales_order)}
:count {:type 'Int} :count {:type 'Int}
@@ -351,16 +304,7 @@
:paid {:type 'String} :paid {:type 'String}
:unpaid {:type 'String}}} :unpaid {:type 'String}}}
:import_ledger_entry_result {:fields {:external_id {:type 'String} :upcoming_transaction {:fields {:amount {:type :money}
:error {:type 'String}
:status {:type 'String}}}
:import_ledger_result {:fields {:successful {:type '(list :import_ledger_entry_result)}
:existing {:type '(list :import_ledger_entry_result)}
:ignored {:type '(list :import_ledger_entry_result)}
:errors {:type '(list :import_ledger_entry_result)}}}
:upcoming_transaction {:fields {:amount {:type :money}
:identifier {:type 'String} :identifier {:type 'String}
:date {:type :iso_date}}} :date {:type :iso_date}}}
@@ -394,20 +338,7 @@
:cash_flow {:type :cash_flow_result :cash_flow {:type :cash_flow_result
:args {:client_id {:type :id}} :args {:client_id {:type :id}}
:resolve :get-cash-flow} :resolve :get-cash-flow}
:balance_sheet {:type :balance_sheet :yodlee_provider_account_page {:type :yodlee_provider_account_page
:args {:client_id {:type :id}
:include_comparison {:type 'Boolean}
:date {:type :iso_date}
:comparison_date {:type :iso_date}}
:resolve :get-balance-sheet}
:profit_and_loss {:type :profit_and_loss_report
:args {:client_id {:type :id}
:client_ids {:type '(list :id)}
:periods {:type '(list :date_range)}}
:resolve :get-profit-and-loss}
:yodlee_provider_account_page {:type :yodlee_provider_account_page
:args {:client_id {:type :id}} :args {:client_id {:type :id}}
:resolve :get-yodlee-provider-account-page} :resolve :get-yodlee-provider-account-page}
@@ -442,9 +373,7 @@
:description {:type 'String}} :description {:type 'String}}
:resolve :get-transaction-rule-page} :resolve :get-transaction-rule-page}
:ledger_page {:type :ledger_page
:args {:filters {:type :ledger_filters}}
:resolve :get-ledger-page}
:sales_order_page {:type :sales_order_page :sales_order_page {:type :sales_order_page
:args {:client_id {:type :id} :args {:client_id {:type :id}
@@ -472,44 +401,9 @@
:sort_name {:type 'String} :sort_name {:type 'String}
:asc {:type 'Boolean}}} :asc {:type 'Boolean}}}
:ledger_filters {:fields {:client_id {:type :id}
:vendor_id {:type :id}
:account_id {:type :id}
:amount_lte {:type :money}
:amount_gte {:type :money}
:bank_account_id {:type :id}
:date_range {:type :date_range}
:location {:type 'String}
:from_numeric_code {:type 'Int}
:to_numeric_code {:type 'Int}
:start {:type 'Int}
:per_page {:type 'Int}
:only_external {:type 'Boolean}
:external_id_like {:type 'String}
:source {:type 'String}
:sort {:type '(list :sort_item)}}}
:date_range {:fields {:start {:type :iso_date} :date_range {:fields {:start {:type :iso_date}
:end {:type :iso_date}}} :end {:type :iso_date}}}
:import_ledger_line_item {:fields {:account_identifier {:type 'String}
:location {:type 'String}
:debit {:type :money}
:credit {:type :money}}}
:import_ledger_entry {:fields {:source {:type 'String}
:external_id {:type 'String}
:client_code {:type 'String}
:date {:type 'String}
:vendor_name {:type 'String}
:amount {:type :money}
:note {:type 'String}
:cleared_against {:type 'String}
:line_items {:type '(list :import_ledger_line_item)}}}
:edit_user :edit_user
{:fields {:id {:type :id} {:fields {:id {:type :id}
:name {:type 'String} :name {:type 'String}
@@ -612,7 +506,6 @@
:type_1099 {:values [{:enum-value :none} :type_1099 {:values [{:enum-value :none}
{:enum-value :misc} {:enum-value :misc}
{:enum-value :landlord}]} {:enum-value :landlord}]}
:applicability {:values [{:enum-value :global} :applicability {:values [{:enum-value :global}
{:enum-value :optional} {:enum-value :optional}
{:enum-value :customized}]} {:enum-value :customized}]}
@@ -623,46 +516,40 @@
{:enum-value :equity} {:enum-value :equity}
{:enum-value :revenue}]}} {:enum-value :revenue}]}}
:mutations :mutations
{:request_import {:type 'String {:request_import
:args {:which {:type 'String}} {:type 'String
:resolve :mutation/request-import} :args {:which {:type 'String}}
:resolve :mutation/request-import}
:delete_external_ledger {:type :message :delete_transaction_rule
:args {:filters {:type :ledger_filters} {:type :id
:ids {:type '(list :id)}} :args {:transaction_rule_id {:type :id}}
:resolve :mutation/delete-external-ledger} :resolve :mutation/delete-transaction-rule}
:merge_vendors
{:type :id
:args {:from {:type :id}
:to {:type :id}}
:resolve :mutation/merge-vendors}
:delete_transaction_rule {:type :id :edit_user
:args {:transaction_rule_id {:type :id}} {:type :user
:resolve :mutation/delete-transaction-rule} :args {:edit_user {:type :edit_user}}
:merge_vendors {:type :id :resolve :mutation/edit-user}
:args {:from {:type :id}
:to {:type :id}}
:resolve :mutation/merge-vendors}
:edit_user {:type :user :upsert_vendor
:args {:edit_user {:type :edit_user}} {:type :vendor
:resolve :mutation/edit-user} :args {:vendor {:type :add_vendor}}
:resolve :mutation/upsert-vendor}
:upsert_transaction_rule
{:type :transaction_rule
:args {:transaction_rule {:type :edit_transaction_rule}}
:resolve :mutation/upsert-transaction-rule}
:upsert_account
:upsert_vendor {:type :vendor {:type :account
:args {:vendor {:type :add_vendor}} :args {:account {:type :edit_account}}
:resolve :mutation/upsert-vendor} :resolve :mutation/upsert-account}}})
:upsert_transaction_rule {:type :transaction_rule
:args {:transaction_rule {:type :edit_transaction_rule}}
:resolve :mutation/upsert-transaction-rule}
:import_ledger {:type :import_ledger_result
:args {:entries {:type '(list :import_ledger_entry)}}
:resolve :mutation/import-ledger}
:upsert_account {:type :account
:args {:account {:type :edit_account}}
:resolve :mutation/upsert-account}
}})
(defn snake->kebab [s] (defn snake->kebab [s]
@@ -881,10 +768,7 @@
:get-yodlee-provider-account-page gq-yodlee2/get-yodlee-provider-account-page :get-yodlee-provider-account-page gq-yodlee2/get-yodlee-provider-account-page
:get-all-sales-orders get-all-sales-orders :get-all-sales-orders get-all-sales-orders
:get-accounts gq-accounts/get-accounts :get-accounts gq-accounts/get-accounts
:get-ledger-page gq-ledger/get-ledger-page
:get-sales-order-page gq-sales-orders/get-sales-orders-page :get-sales-order-page gq-sales-orders/get-sales-orders-page
:get-balance-sheet gq-ledger/get-balance-sheet
:get-profit-and-loss gq-ledger/get-profit-and-loss
:get-transaction-rule-page gq-transaction-rules/get-transaction-rule-page :get-transaction-rule-page gq-transaction-rules/get-transaction-rule-page
:get-transaction-rule-matches gq-transaction-rules/get-transaction-rule-matches :get-transaction-rule-matches gq-transaction-rules/get-transaction-rule-matches
:get-expense-account-stats get-expense-account-stats :get-expense-account-stats get-expense-account-stats
@@ -895,17 +779,16 @@
:get-user get-user :get-user get-user
:mutation/delete-transaction-rule gq-transaction-rules/delete-transaction-rule :mutation/delete-transaction-rule gq-transaction-rules/delete-transaction-rule
:mutation/edit-user gq-users/edit-user :mutation/edit-user gq-users/edit-user
:mutation/delete-external-ledger gq-ledger/delete-external-ledger
:mutation/upsert-transaction-rule gq-transaction-rules/upsert-transaction-rule :mutation/upsert-transaction-rule gq-transaction-rules/upsert-transaction-rule
:test-transaction-rule gq-transaction-rules/test-transaction-rule :test-transaction-rule gq-transaction-rules/test-transaction-rule
:run-transaction-rule gq-transaction-rules/run-transaction-rule :run-transaction-rule gq-transaction-rules/run-transaction-rule
:mutation/upsert-vendor gq-vendors/upsert-vendor :mutation/upsert-vendor gq-vendors/upsert-vendor
:mutation/upsert-account gq-accounts/upsert-account :mutation/upsert-account gq-accounts/upsert-account
:mutation/merge-vendors gq-vendors/merge-vendors :mutation/merge-vendors gq-vendors/merge-vendors
:mutation/import-ledger gq-ledger/import-ledger
:mutation/request-import gq-requests/request-import :mutation/request-import gq-requests/request-import
:get-vendor gq-vendors/get-graphql}) :get-vendor gq-vendors/get-graphql})
gq-checks/attach gq-checks/attach
gq-ledger/attach
gq-plaid/attach gq-plaid/attach
gq-import-batches/attach gq-import-batches/attach
gq-transactions/attach gq-transactions/attach

View File

@@ -1,20 +1,23 @@
(ns auto-ap.graphql.ledger (ns auto-ap.graphql.ledger
(:require [auto-ap.datomic :refer [audit-transact-batch remove-nils uri conn]] (:require
[auto-ap.datomic.accounts :as a] [auto-ap.datomic :refer [audit-transact-batch conn remove-nils uri]]
[auto-ap.datomic.clients :as d-clients] [auto-ap.datomic.accounts :as a]
[auto-ap.datomic.ledger :as l] [auto-ap.datomic.clients :as d-clients]
[auto-ap.datomic.vendors :as d-vendors] [auto-ap.datomic.ledger :as l]
[auto-ap.graphql.utils [auto-ap.datomic.vendors :as d-vendors]
:refer [auto-ap.graphql.utils
[->graphql <-graphql assert-admin assert-can-see-client result->page]] :refer [->graphql <-graphql assert-admin assert-can-see-client result->page]]
[auto-ap.parse.util :as parse] [auto-ap.parse.util :as parse]
[auto-ap.utils :refer [by dollars=]] [auto-ap.utils :refer [by dollars=]]
[clj-time.coerce :as coerce] [auto-ap.pdf.ledger :refer [print-pnl]]
[clojure.tools.logging :as log] [clj-time.coerce :as coerce]
[datomic.api :as d] [clojure.tools.logging :as log]
[unilog.context :as lc] [com.walmartlabs.lacinia.util :refer [attach-resolvers]]
[mount.core :as mount] [datomic.api :as d]
[yang.scheduler :as scheduler])) [mount.core :as mount]
[unilog.context :as lc]
[yang.scheduler :as scheduler]))
(mount/defstate running-balance-cache (mount/defstate running-balance-cache
:start (atom {})) :start (atom {}))
@@ -205,6 +208,34 @@
#(roll-up-until (lookup-account %) (all-ledger-entries %) (coerce/to-date end) (coerce/to-date start) ) #(roll-up-until (lookup-account %) (all-ledger-entries %) (coerce/to-date end) (coerce/to-date start) )
client-ids)})))}))) client-ids)})))})))
(defn profit-and-loss-pdf [context args value]
(let [data (get-profit-and-loss context args value)
result (print-pnl args data)]
(->graphql {:report_url result}))
#_(let [client-id (:client_id args)
client-ids (or (some-> client-id vector)
(filter identity (:client_ids args)))
_ (when (not (seq client-ids))
(throw (ex-info "Please select a client." {:validation-error "Please select a client."})))
_ (doseq [client-id client-ids]
(assert-can-see-client (:id context) client-id))
all-ledger-entries (->> client-ids
(map (fn [client-id]
[client-id (full-ledger-for-client client-id)]))
(into {}))
lookup-account (->> client-ids
(map (fn [client-id]
[client-id (build-account-lookup client-id)]))
(into {}))]
(->graphql
{:periods
(->> (:periods args)
(mapv (fn [{:keys [start end]}]
{:accounts (mapcat
#(roll-up-until (lookup-account %) (all-ledger-entries %) (coerce/to-date end) (coerce/to-date start) )
client-ids)})))})))
(defn assoc-error [f] (defn assoc-error [f]
(fn [entry] (fn [entry]
@@ -490,3 +521,169 @@
:start (scheduler/every (* 15 60 1000) refresh-running-balance-cache) :start (scheduler/every (* 15 60 1000) refresh-running-balance-cache)
:stop (scheduler/stop running-balance-cache-worker)) :stop (scheduler/stop running-balance-cache-worker))
(def objects
{:balance_sheet_account
{:fields {:id {:type 'String}
:amount {:type 'String}
:location {:type 'String}
:client_id {:type :id}
:count {:type 'Int}
:numeric_code {:type 'Int}
:account_type {:type :account_type}
:name {:type 'String}}}
:profit_and_loss_pdf
{:fields {:report_url {:type 'String}}}
:balance_sheet
{:fields {:balance_sheet_accounts {:type '(list :balance_sheet_account)}
:comparable_balance_sheet_accounts {:type '(list :balance_sheet_account)}}}
:profit_and_loss_report_period
{:fields {:accounts {:type '(list :balance_sheet_account)}}}
:profit_and_loss_report
{:fields {:periods {:type '(list :profit_and_loss_report_period)}}}
:journal_entry_line
{:fields {:id {:type :id}
:account {:type :account}
:location {:type 'String}
:debit {:type 'String}
:credit {:type 'String}
:running_balance {:type :money}}}
:journal_entry
{:fields {:id {:type :id}
:source {:type 'String}
:external_id {:type 'String}
:original_entity {:type :id}
:amount {:type 'String}
:note {:type 'String}
:cleared_against {:type 'String}
:client {:type :client}
:vendor {:type :vendor}
:alternate_description {:type 'String}
:date {:type 'String}
:line_items {:type '(list :journal_entry_line)}}}
:ledger_page
{:fields {:journal_entries {:type '(list :journal_entry)}
:count {:type 'Int}
:total {:type 'Int}
:start {:type 'Int}
:end {:type 'Int}}}
:import_ledger_entry_result
{:fields {:external_id {:type 'String}
:error {:type 'String}
:status {:type 'String}}}
:import_ledger_result
{:fields {:successful {:type '(list :import_ledger_entry_result)}
:existing {:type '(list :import_ledger_entry_result)}
:ignored {:type '(list :import_ledger_entry_result)}
:errors {:type '(list :import_ledger_entry_result)}}}})
(def queries
{:balance_sheet {:type :balance_sheet
:args {:client_id {:type :id}
:include_comparison {:type 'Boolean}
:date {:type :iso_date}
:comparison_date {:type :iso_date}}
:resolve :get-balance-sheet}
:profit_and_loss {:type :profit_and_loss_report
:args {:client_id {:type :id}
:client_ids {:type '(list :id)}
:periods {:type '(list :date_range)}}
:resolve :get-profit-and-loss}
:profit_and_loss_pdf {:type :profit_and_loss_pdf
:args {:client_id {:type :id}
:client_ids {:type '(list :id)}
:periods {:type '(list :date_range)}}
:resolve :profit-and-loss-pdf}
:ledger_page {:type :ledger_page
:args {:filters {:type :ledger_filters}}
:resolve :get-ledger-page}})
(def mutations
{:import_ledger
{:type :import_ledger_result
:args {:entries {:type '(list :import_ledger_entry)}}
:resolve :mutation/import-ledger}
:delete_external_ledger
{:type :message
:args {:filters {:type :ledger_filters}
:ids {:type '(list :id)}}
:resolve :mutation/delete-external-ledger}})
(def input-objects
{:ledger_filters
{:fields {:client_id {:type :id}
:vendor_id {:type :id}
:account_id {:type :id}
:amount_lte {:type :money}
:amount_gte {:type :money}
:bank_account_id {:type :id}
:date_range {:type :date_range}
:location {:type 'String}
:from_numeric_code {:type 'Int}
:to_numeric_code {:type 'Int}
:start {:type 'Int}
:per_page {:type 'Int}
:only_external {:type 'Boolean}
:external_id_like {:type 'String}
:source {:type 'String}
:sort {:type '(list :sort_item)}}}
:import_ledger_line_item
{:fields {:account_identifier {:type 'String}
:location {:type 'String}
:debit {:type :money}
:credit {:type :money}}}
:import_ledger_entry
{:fields {:source {:type 'String}
:external_id {:type 'String}
:client_code {:type 'String}
:date {:type 'String}
:vendor_name {:type 'String}
:amount {:type :money}
:note {:type 'String}
:cleared_against {:type 'String}
:line_items {:type '(list :import_ledger_line_item)}}}
})
(def enums
{:payment_type {:values [{:enum-value :check}
{:enum-value :cash}
{:enum-value :debit}
{:enum-value :credit}]}
:payment_status {:values [{:enum-value :voided}
{:enum-value :pending}
{:enum-value :cleared}]}})
(def resolvers
{:get-ledger-page get-ledger-page
:get-balance-sheet get-balance-sheet
:get-profit-and-loss get-profit-and-loss
:profit-and-loss-pdf profit-and-loss-pdf
:mutation/delete-external-ledger delete-external-ledger
:mutation/import-ledger import-ledger})
(defn attach [schema]
(->
(merge-with merge schema
{:objects objects
:queries queries
:mutations mutations
:input-objects input-objects
:enums enums})
(attach-resolvers resolvers)))

View File

@@ -0,0 +1,456 @@
(ns auto-ap.pdf.ledger
(:require
[amazonica.aws.s3 :as s3]
[auto-ap.datomic :refer [conn]]
[auto-ap.graphql.utils :refer [<-graphql]]
[auto-ap.time :as atime]
[auto-ap.utils :refer [dollars-0?]]
[clj-pdf.core :as pdf]
[clojure.java.io :as io]
[clojure.string :as str]
[clojure.walk :refer [postwalk]]
[config.core :refer [env]]
[datomic.api :as d])
(:import
(java.io ByteArrayOutputStream)
(java.util UUID)))
(defn distribute [nums]
(let [sum (reduce + 0 nums)]
(map #(* 100 (/ % sum)) nums)))
(defn date->str [d]
(atime/unparse-local d atime/normal-date))
(def ranges
{:sales [40000 49999]
:cogs [50000 59999]
:payroll [60000 69999]
:controllable [70000 79999]
:fixed-overhead [80000 89999]
:ownership-controllable [90000 99999]})
(def groupings
{:sales [["40000-43999 Food Sales " 40000 43999]
["44000-46999 Alcohol Sales" 44000 46999]
["47000 Merchandise Sales" 47000 47999]
["48000 Other Operating Income" 48000 48999]
["49000 Non-Business Income" 49000 49999]]
:cogs [
["50000-54000 Food Costs" 50000 53999]
["54000-56000 Alcohol Costs" 54000 55999]
["56000 Merchandise Costs" 56000 56999]
["57000-60000 Other Costs of Sales" 57000 59999]]
:payroll [["60000 Payroll - General" 60000 60999]
["61000 Payroll - Management" 61000 61999]
["62000 Payroll - BOH" 62000 62999]
["63000-66000 Payroll - FOH" 63000 65999]
["66000-70000 Payroll - Other" 66000 69999]]
:controllable [["70000 72000 GM Controllable Costs - Ops Related" 70000 71999]
["72000 GM Controllable Costs - Customer Related" 72000 72999]
["73000 GM Controllable Costs - Employee Related" 73000 73999]
["74000 GM Controllable Costs - Building & Equipment Related" 74000 74999]
["75000 GM Controllable Costs - Office & Management Related" 75000 75999]
["76000-80000 GM Controllable Costs - Other" 76000 79999]]
:fixed-overhead [["80000-82000 Operational Costs" 80000 81999]
["82000 Occupancy Costs" 82000 82999]
["83000 Utility Costs" 83000 83999]
["84000 Equipment Rental" 84000 84999]
["85000-87000 Taxes & Insurance" 85000 86999]
["87000-90000 Other Non-Controllable Costs" 87000 89999]]
:ownership-controllable [["90000-93000 Research & Entertainment" 90000 92999]
["93000 Bank Charges & Interest" 93000 93999]
["94000-96000 Other Owner Controllable Costs" 94000 95999]
["96000 Depreciation" 96000 96999]
["97000 Taxes" 97000 97999]
["98000 Other Expenses" 98000 98999]]})
(defn in-range? [code]
(reduce
(fn [acc [start end]]
(if (<= start code end)
(reduced true)
acc))
false
(vals ranges)))
(defn locations [data]
(->> data
:periods
(mapcat :accounts)
(filter (comp in-range? :numeric-code))
(group-by (juxt :client-id :location))
(filter (fn [[k as]]
(not (dollars-0? (reduce + 0 (map :amount as))))))
(mapcat second)
(map (fn [a]
(if (or (not (:client-id a))
(empty? (:location a)))
nil
[(:client-id a)
(:location a)])))
(filter identity)
(set)
(sort-by (fn [x]
[(:client-id x)
(if (= (:location x) "HQ" )
"ZZZZZZ"
(:location x))]))))
(defn expand [data]
(postwalk (fn [x]
(cond
(map-entry? x)
x
(sequential? x)
(vec (mapcat (fn [r]
(cond (and (sequential? r)
(= :<> (first r)))
(filter identity (rest r))
:else
[r]))
x))
:else
x
))
data))
(defn map-periods [for-every between periods include-deltas]
(into [:<>]
(for [[_ i] (map vector periods (range))]
[:<> (for-every i)
(if (and include-deltas (not= 0 i))
(between i))])))
(defn period-header [{:keys [include_deltas periods]}]
[
[[:cell "Period"]
[:<>
(map-periods
(fn [i]
[:cell {:colspan 2}
(str (date->str (get-in periods [i :start])) " - " (date->str (get-in periods [i :end])))])
(fn [i]
[:cell ""])
periods
include_deltas)]]
[[:cell ""]
[:<> (map-periods
(fn [i]
[:<>
[:cell
"Amount"]
[:cell
"% Sales"]])
(fn [i]
[:cell "𝝙"])
periods
include_deltas)]
]])
(defn all-accounts [data]
(transduce
(comp
(map #(->> (:accounts %)
(group-by (juxt :numeric-code :client-id :location))
(map (fn [[k v]]
[k
(reduce (fn [a n]
(-> a
(update :count (fn [z] (+ z (:count n))))
(update :amount (fn [z] (+ z (:amount n))))))
(first v)
(rest v))]))
(into {}))))
conj
[]
(:periods data)))
(defn filter-accounts [accounts period [from to] only-client only-location]
(->> (get accounts period)
vals
(filter (fn [{:keys [location client-id numeric-code]}]
(and (or (nil? only-location)
(= only-location location))
(or (nil? only-client)
(= only-client client-id))
(<= from numeric-code to))))
(sort-by :numeric-code)))
(defn aggregate-accounts [accounts]
(reduce (fnil + 0.0) 0.0 (map :amount accounts)))
(defn used-accounts [accounts [from to] client-id location]
(->> accounts
(mapcat vals)
(filter #(<= from (:numeric-code %) to))
(filter #(= client-id (:client-id %)))
(filter #(= location (:location %)))
(map #(select-keys % [:numeric-code :name]))
(set)
(sort-by :numeric-code)))
(defn subtotal-row [args data types negs title client-id location]
(let [all-accounts (all-accounts data)
raw (map-indexed
(fn [i p]
(aggregate-accounts (mapcat (fn [t]
(cond->> (filter-accounts all-accounts i (ranges t) client-id location)
(negs t) (map #(update % :amount -))))
types))
)
(:periods args))
sales (map-indexed
(fn [i _]
(aggregate-accounts (filter-accounts all-accounts i (ranges :sales) client-id location)))
(:periods args))
deltas (->> raw
(partition-all 2)
(map (fn [[a b]]
(- b
a))))]
(into [title]
(->> raw
(map (fn [s r]
[r (if (dollars-0? s)
0.0
(/ r s))])
sales)
(partition-all 2)
(mapcat (fn [d [[a a-sales] [b b-sales]]]
[a a-sales b b-sales d]
)
deltas)))))
(defn location-summary-table [args data client-id location]
[(subtotal-row args data [:sales] #{} "Sales" client-id location)
(subtotal-row args data [:cogs ] #{} "Cogs" client-id location)
(subtotal-row args data [:payroll ]#{} "Payroll" client-id location)
(subtotal-row args data [:sales :payroll :cogs] #{:payroll :cogs} "Gross Profits" client-id location)
(subtotal-row args data [:controllable :fixed-overhead :ownership-controllable] #{} "Overhead" client-id location)
(subtotal-row args data [:sales :cogs :payroll :controllable :fixed-overhead :ownership-controllable] #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable} "Net Income" client-id location)])
(defn detail-sub-rows [args data grouping client-id location]
(let [all-accounts (all-accounts data)]
(for [[grouping-name from to] grouping
:let [account-codes (used-accounts all-accounts [from to] client-id location)]
:when (seq account-codes)]
(->
[[(str "---" grouping-name "---")]]
(into (for [{:keys [numeric-code name]} account-codes]
(let [raw (map-indexed
(fn [i p]
(get-in all-accounts [i [numeric-code client-id location] :amount] 0.0))
(:periods args))
sales (map-indexed
(fn [i _]
(aggregate-accounts (filter-accounts all-accounts i (ranges :sales) client-id location)))
(:periods args))
deltas (->> raw
(partition-all 2)
(map (fn [[a b]]
(- b
a))))]
(into [name]
(->> raw
(map (fn [s r]
[r (if (dollars-0? s)
0.0
(/ r s))])
sales)
(partition-all 2)
(mapcat (fn [d [[a a-sales] [b b-sales]]]
[a a-sales b b-sales d]
)
deltas))))
#_[:tr
[:td name]
#_(map-periods
(fn [i]
(let [amount (get-in all-accounts [i [numeric-code client-id location] :amount] 0.0)]
[:<>
[:td.has-text-right (if multi-client?
[:span (->$ amount)]
[:a {:on-click (dispatch-event [::investigate-clicked location numeric-code numeric-code i :current])
:disabled (boolean multi-client?)}
(->$ amount)])]
[:td.has-text-right (->% (percent-of-sales amount all-accounts i client-id location))]]))
(fn [i]
[:td.has-text-right (->$ (- (get-in all-accounts [i [numeric-code client-id location] :amount] 0.0)
(get-in all-accounts [(dec i) [numeric-code client-id location] :amount] 0.0)))])
periods
include-deltas)])))
)))
(defn detail-rows [args data type title client-id location]
(-> [[title]]
(into (detail-sub-rows args data (type groupings) client-id location))
(conj (subtotal-row args data [type] #{} title client-id location))))
(defn location-detail-table [args data client-id location]
(-> []
(into (detail-rows args data :sales (str location " Sales") client-id location))))
(defn summarize-pnl [args data]
{:summaries (for [[client-id location] (locations data)]
(location-summary-table args data client-id location))
:details (for [[client-id location] (locations data)]
(location-detail-table args data client-id location))}
)
(defn make-pnl [args data]
(let [data (<-graphql data)
args (<-graphql args)
_ (clojure.pprint/pprint (summarize-pnl (assoc args :deltas true) data))output-stream (ByteArrayOutputStream.)
_ (println (:client_ids args))
clients (d/pull-many (d/db conn) '[:client/name] (:client-ids args))]
(pdf/pdf
(expand
[{:left-margin 25 :right-margin 0 :top-margin 0 :bottom-margin 0 :size :letter}
[:heading (str "Profit and Loss - " (str/join ", " (map :client/name clients)))]
#_(for [[client-id location] (locations data)]
^{:key (str client-id "-" location "-summary")}
(location-summary args data client-id location (:include-deltas data))
)
#_(let [{:keys [bank-account paid-to client check date amount memo] {print-as :vendor/print-as vendor-name :vendor/name :as vendor} :vendor} check
df (DecimalFormat. "#,###.00")]
[:table {:num-cols 6 :border false :leading 11 :widths (distribute [2 2 2 2 2 2])}
[(let [{:keys [:client/name] {:keys [:address/street1 :address/city :address/state :address/zip]} :client/address} client]
[:cell {:colspan 4 } [:paragraph {:leading 14} name "\n" street1 "\n" (str city ", " state " " zip)] ])
(let [{:keys [:bank-account/bank-name :bank-account/bank-code] } bank-account]
[:cell {:colspan 6 :align :center} [:paragraph {:style :bold} bank-name] [:paragraph {:size 8 :leading 8} bank-code]])
[:cell {:colspan 2 :size 13}
check]]
[[:cell {:colspan 9}]
[:cell {:colspan 3 :leading -10} date]]
[[:cell {:colspan 12 :size 14}]
]
[[:cell {:size 13 :leading 13} "PAY"]
[:cell {:size 8 :leading 8 } "TO THE ORDER OF"]
[:cell {:colspan 7} (if (seq print-as)
print-as
vendor-name)]
[:cell {:colspan 3} amount]]
[[:cell {}]
[:cell {:colspan 8} (str " -- " word-amount " " (str/join "" (take (max
2
(- 95
(count word-amount)))
(repeat "-"))))
[:line {:line-width 0.15 :color [50 50 50]}]]
[:cell {:colspan 3}]]
[[:cell {:size 9 :leading 11.5} "\n\n\n\n\nMEMO"]
[:cell {:colspan 5 :leading 11.5} (split-memo memo)
[:line {:line-width 0.15 :color [50 50 50]}]]
[:cell {:colspan 6 } (if (:client/signature-file client)
[:image { :top-margin 90 :xscale 0.30 :yscale 0.30 :align :center}
(:client/signature-file client)]
[:spacer])]]
#_[
#_[:cell {:colspan 5} #_memo ]
#_[:cell {:colspan 6}]]
[[:cell {:colspan 2}]
[:cell {:colspan 10 :leading 30}
[:phrase {:size 18 :ttf-name "public/micrenc.ttf"} (str "c" check "c a" (:bank-account/routing bank-account) "a " (:bank-account/number bank-account) "c")]]]
[[:cell {:colspan 12 :leading 18} [:spacer]]]
[[:cell]
(into
[:cell {:colspan 9}]
(let [{:keys [:client/name]
{:keys [:address/street1 :address/street2 :address/city :address/state :address/zip ]} :client/address} client]
(filter identity
(list
[:paragraph " " name]
[:paragraph " " street1]
(when (not (str/blank? street2))
[:paragraph " " street2])
[:paragraph " " city ", " state " " zip]))))
[:cell {:colspan 2 :size 13}
check]]
[[:cell {:colspan 12 :leading 74} [:spacer]]]
[[:cell]
[:cell {:colspan 5} [:paragraph
" " vendor-name "\n"
" " (:address/street1 (:vendor/address vendor)) "\n"
(when (not (str/blank? (:address/street2 (:vendor/address vendor))))
(str " " (:address/street2 (:vendor/address vendor)) "\n")
)
" " (:address/city (:vendor/address vendor)) ", " (:address/state (:vendor/address vendor)) " " (:address/zip (:vendor/address vendor))]]
[:cell {:align :right}
"Paid to:\n"
"Amount:\n"
"Date:\n"]
[:cell {:colspan 5}
[:paragraph paid-to]
[:paragraph amount]
[:paragraph date]]]
[[:cell {:colspan 3} "Memo:"]
[:cell {:colspan 9} memo]]
[[:cell {:colspan 12} [:spacer]]]
[[:cell {:colspan 12} [:spacer]]]
[[:cell {:colspan 12} [:spacer]]]
[[:cell {:colspan 12} [:spacer]]]
[[:cell {:colspan 5}]
[:cell {:align :right :colspan 2}
"Check:\n"
"Vendor:\n"
"Company:\n"
"Bank Account:\n"
"Paid To:\n"
"Amount:\n"
"Date:\n"]
[:cell {:colspan 5}
[:paragraph check]
[:paragraph vendor-name]
[:paragraph (:client/name client)]
[:paragraph (:bank-account/bank-name bank-account)]
[:paragraph paid-to]
[:paragraph amount]
[:paragraph date]]]
[[:cell {:colspan 3} "Memo:"]
[:cell {:colspan 9} memo]]
])])
output-stream)
(.toByteArray output-stream)))
(defn print-pnl [args data]
(let [uuid (str (UUID/randomUUID))
pdf-data (make-pnl args data)]
(s3/put-object :bucket-name (:data-bucket env)
:key (str "reports/pnl/" uuid ".pdf")
:input-stream (io/make-input-stream pdf-data {})
:metadata {:content-length (count pdf-data)
:content-type "application/pdf"})
(str "http://" (:data-bucket env) ".s3-website-us-east-1.amazonaws.com/reports/pnl/" uuid ".pdf")))

View File

@@ -212,8 +212,31 @@
:clients (mapv #(select-keys % [:name :id]) (:clients (:data db))) } :clients (mapv #(select-keys % [:name :id]) (:clients (:data db))) }
:db (dissoc db :report)}))) :db (dissoc db :report)})))
(re-frame/reg-event-fx
::received-pdf
(fn [_ [_ result]]
(println result)
{:dispatch [::modal/modal-requested {:title "Your report is ready"
:body [:div
[:div "Click " [:a {:href (-> result :profit-and-loss-pdf :report-url) :target "_new"} "here"] " to view it."]]}]}))
(re-frame/reg-event-fx
::export-pdf
[with-user (forms/in-form ::form)]
(fn [{:keys [db user] :as cofx}]
(cond-> {:graphql {:token user
:owns-state {:single ::page}
:query-obj {:venia/queries [[:profit-and-loss-pdf
{:client-ids (map :id (:clients (:data db)))
:periods (mapv (fn [[start end] ] {:start (date->str start standard) :end (date->str end standard)} )
(:periods (:data db)))}
[:report_url]]]}
:on-success [::received-pdf]}
:set-uri-params {:periods (mapv (fn [[start end title]]
[(date->str start standard)
(date->str end standard)])
(:periods (:data db)))
:clients (mapv #(select-keys % [:name :id]) (:clients (:data db))) }
:db (dissoc db :report)})))
(re-frame/reg-event-db (re-frame/reg-event-db
@@ -876,7 +899,11 @@
:label "Include deltas" :label "Include deltas"
:type "checkbox"}]]]] :type "checkbox"}]]]]
[:div.level-right [:div.level-right
[:button.button.is-primary "Run"]]] [:div.buttons
[:button.button.is-secondary {:on-click (dispatch-event [::export-pdf])} "Export"]
[:button.button.is-primary "Run"]]
]]
[:div.report-control-detail {:ref (fn [el] [:div.report-control-detail {:ref (fn [el]
(when-not @!box (when-not @!box
(reset! !box el)))}]])))) (reset! !box el)))}]]))))