cash flows
This commit is contained in:
@@ -152,7 +152,7 @@ const getFourWeekPeriods = endDate => {
|
|||||||
if (!endDate) {
|
if (!endDate) {
|
||||||
endDate= formatDateMMDDYYYY(new Date())
|
endDate= formatDateMMDDYYYY(new Date())
|
||||||
}
|
}
|
||||||
let periods = [];
|
let periods = [endDate];
|
||||||
for (let i = 0; i < 13; i++) {
|
for (let i = 0; i < 13; i++) {
|
||||||
const currentDate = new Date(parseMMDDYYYY(endDate).getTime());
|
const currentDate = new Date(parseMMDDYYYY(endDate).getTime());
|
||||||
currentDate.setDate(currentDate.getDate() - 28 * (i + 1));
|
currentDate.setDate(currentDate.getDate() - 28 * (i + 1));
|
||||||
@@ -161,6 +161,27 @@ const getFourWeekPeriods = endDate => {
|
|||||||
return periods;
|
return periods;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getFourWeekPeriodsPeriods(endDate) {
|
||||||
|
// Determine today's date based on data or current local date
|
||||||
|
const today = endDate ? parseMMDDYYYY(endDate) : new Date();
|
||||||
|
|
||||||
|
// Calculate the total period
|
||||||
|
const totalStart = dateFns.addDays(dateFns.subWeeks(today, 13 * 4), 1);
|
||||||
|
|
||||||
|
// Construct the array of periods
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
start: dateFns.format(totalStart, 'MM/dd/yyyy'),
|
||||||
|
end: dateFns.format(today, 'MM/dd/yyyy'),
|
||||||
|
title: "Total"
|
||||||
|
},
|
||||||
|
...Array.from({ length: 13 }, (_, i) => ({
|
||||||
|
start: dateFns.format(dateFns.addDays(dateFns.subWeeks(today, (i + 1) * 4), 1), 'MM/dd/yyyy'),
|
||||||
|
end: dateFns.format(dateFns.subWeeks(today, i * 4), 'MM/dd/yyyy')
|
||||||
|
}))
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
const withLastYear = (date) => {
|
const withLastYear = (date) => {
|
||||||
|
|
||||||
if (!date) {
|
if (!date) {
|
||||||
@@ -178,6 +199,38 @@ const withLastYear = (date) => {
|
|||||||
return [formatDateMMDDYYYY(originalDate), formatDateMMDDYYYY(priorYearDate)];
|
return [formatDateMMDDYYYY(originalDate), formatDateMMDDYYYY(priorYearDate)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lastYearPeriod = (date) => {
|
||||||
|
if (!date) {
|
||||||
|
date= new Date()
|
||||||
|
} else {
|
||||||
|
date = parseMMDDYYYY(date)
|
||||||
|
}
|
||||||
|
const originalYear = date.getFullYear();
|
||||||
|
const priorYear = originalYear - 1;
|
||||||
|
|
||||||
|
// Create new Date objects for both years
|
||||||
|
const originalDate = new Date(originalYear, date.getMonth(), date.getDate());
|
||||||
|
const priorYearDate = new Date(priorYear, date.getMonth(), date.getDate());
|
||||||
|
|
||||||
|
return {end: formatDateMMDDYYYY(originalDate), start: formatDateMMDDYYYY(priorYearDate)};
|
||||||
|
}
|
||||||
|
|
||||||
|
const calendarYearPeriod = (date) => {
|
||||||
|
if (!date) {
|
||||||
|
date= new Date()
|
||||||
|
} else {
|
||||||
|
date = parseMMDDYYYY(date)
|
||||||
|
}
|
||||||
|
const originalYear = date.getFullYear();
|
||||||
|
const priorYear = originalYear - 1;
|
||||||
|
|
||||||
|
// Create new Date objects for both years
|
||||||
|
const start = new Date(originalYear, 0, 1);
|
||||||
|
const end = new Date(originalYear, 11, 31);
|
||||||
|
|
||||||
|
return {end: formatDateMMDDYYYY(end), start: formatDateMMDDYYYY(start)};
|
||||||
|
}
|
||||||
|
|
||||||
initMultiDatepicker = function(elem, startingValue) {
|
initMultiDatepicker = function(elem, startingValue) {
|
||||||
const modalParent = elem.closest('#modal-content');
|
const modalParent = elem.closest('#modal-content');
|
||||||
if (modalParent) {
|
if (modalParent) {
|
||||||
@@ -185,7 +238,15 @@ initMultiDatepicker = function(elem, startingValue) {
|
|||||||
} else {
|
} else {
|
||||||
return new Datepicker(elem, {format: "mm/dd/yyyy", autohide: false, maxNumberOfDates: 12, dateDelimiter: ", "});
|
return new Datepicker(elem, {format: "mm/dd/yyyy", autohide: false, maxNumberOfDates: 12, dateDelimiter: ", "});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initCalendar = function(elem, startingValue) {
|
||||||
|
const modalParent = elem.closest('#modal-content');
|
||||||
|
if (modalParent) {
|
||||||
|
return new Datepicker(elem, {format: "mm/dd/yyyy", autohide: false, container: "#modal-content .modal-card"});
|
||||||
|
} else {
|
||||||
|
return new Datepicker(elem, {format: "mm/dd/yyyy", autohide: false, });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -39,7 +39,8 @@
|
|||||||
(def money-input inputs/money-input-)
|
(def money-input inputs/money-input-)
|
||||||
(def int-input inputs/int-input-)
|
(def int-input inputs/int-input-)
|
||||||
(def date-input inputs/date-input-)
|
(def date-input inputs/date-input-)
|
||||||
(def multi-date-input inputs/multi-date-input-)
|
(def multi-calendar-input inputs/multi-calendar-input-)
|
||||||
|
(def calendar-input inputs/calendar-input-)
|
||||||
(def hidden inputs/hidden-)
|
(def hidden inputs/hidden-)
|
||||||
(def select inputs/select-)
|
(def select inputs/select-)
|
||||||
(def typeahead inputs/typeahead-)
|
(def typeahead inputs/typeahead-)
|
||||||
|
|||||||
@@ -89,7 +89,7 @@
|
|||||||
(let [selected (cond
|
(let [selected (cond
|
||||||
(#{::invoice-route/all-page ::invoice-route/unpaid-page ::invoice-route/voided-page ::invoice-route/paid-page ::oi-routes/new ::invoice-route/import-page :invoice-glimpse :invoice-glimpse-textract-invoice} (:matched-route request))
|
(#{::invoice-route/all-page ::invoice-route/unpaid-page ::invoice-route/voided-page ::invoice-route/paid-page ::oi-routes/new ::invoice-route/import-page :invoice-glimpse :invoice-glimpse-textract-invoice} (:matched-route request))
|
||||||
"invoices"
|
"invoices"
|
||||||
|
|
||||||
(#{:pos-sales :pos-expected-deposits :pos-tenders :pos-refunds :pos-cash-drawer-shifts} (:matched-route request))
|
(#{:pos-sales :pos-expected-deposits :pos-tenders :pos-refunds :pos-cash-drawer-shifts} (:matched-route request))
|
||||||
"sales"
|
"sales"
|
||||||
(#{::payment-routes/all-page ::payment-routes/pending-page ::payment-routes/cleared-page ::payment-routes/voided-page} (:matched-route request))
|
(#{::payment-routes/all-page ::payment-routes/pending-page ::payment-routes/cleared-page ::payment-routes/voided-page} (:matched-route request))
|
||||||
@@ -152,15 +152,15 @@
|
|||||||
|
|
||||||
|
|
||||||
#_(when (can? (:identity request)
|
#_(when (can? (:identity request)
|
||||||
{:subject :invoice
|
{:subject :invoice
|
||||||
:activity :import})
|
:activity :import})
|
||||||
(menu-button- {:href (hu/url (bidi/path-for ssr-routes/only-routes
|
(menu-button- {:href (hu/url (bidi/path-for ssr-routes/only-routes
|
||||||
:invoice-glimpse))
|
:invoice-glimpse))
|
||||||
:active? (= :invoice-glimpse (:matched-route request))
|
:active? (= :invoice-glimpse (:matched-route request))
|
||||||
:hx-boost "true"}
|
:hx-boost "true"}
|
||||||
[:div.flex.gap-2
|
[:div.flex.gap-2
|
||||||
"Glimpse"
|
"Glimpse"
|
||||||
(tags/pill- {:color :secondary} "Beta")]))
|
(tags/pill- {:color :secondary} "Beta")]))
|
||||||
|
|
||||||
|
|
||||||
(when (can? (:identity request)
|
(when (can? (:identity request)
|
||||||
@@ -303,8 +303,20 @@
|
|||||||
:profit-and-loss-detail)} "Profit & Loss Detail")
|
:profit-and-loss-detail)} "Profit & Loss Detail")
|
||||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||||
:cash-flows)} "Cash Flows")
|
:cash-flows)} "Cash Flows")
|
||||||
|
(if (is-admin? (:identity request))
|
||||||
(if (is-admin? (:identity request))
|
(menu-button- {:href (hu/url (bidi/path-for ssr-routes/only-routes
|
||||||
|
::ledger-routes/cash-flows))
|
||||||
|
:active? (= ::ledger-routes/balance-sheet (:matched-route request))
|
||||||
|
:hx-boost "true"}
|
||||||
|
[:div.flex.gap-2
|
||||||
|
"Cash flows"
|
||||||
|
(tags/pill- {:color :secondary} "WIP")])
|
||||||
|
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||||
|
:cash-flows)} "Cash flows"))
|
||||||
|
(when (is-admin? (:identity request))
|
||||||
|
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||||
|
:cash-flows)} "Old Cash flows"))
|
||||||
|
(if (is-admin? (:identity request))
|
||||||
(menu-button- {:href (hu/url (bidi/path-for ssr-routes/only-routes
|
(menu-button- {:href (hu/url (bidi/path-for ssr-routes/only-routes
|
||||||
::ledger-routes/balance-sheet))
|
::ledger-routes/balance-sheet))
|
||||||
:active? (= ::ledger-routes/balance-sheet (:matched-route request))
|
:active? (= ::ledger-routes/balance-sheet (:matched-route request))
|
||||||
@@ -313,16 +325,15 @@
|
|||||||
"Balance Sheet"
|
"Balance Sheet"
|
||||||
(tags/pill- {:color :secondary} "WIP")])
|
(tags/pill- {:color :secondary} "WIP")])
|
||||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||||
:balance-sheet)} "Balance Sheet"))
|
:balance-sheet)} "Balance Sheet"))
|
||||||
(when (is-admin? (:identity request))
|
(when (is-admin? (:identity request))
|
||||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||||
:balance-sheet)} "Old Balance Sheet"))
|
:balance-sheet)} "Old Balance Sheet"))
|
||||||
(when (can? (:identity request)
|
(when (can? (:identity request)
|
||||||
{:subject :ledger
|
{:subject :ledger
|
||||||
:activity :import})
|
:activity :import})
|
||||||
(menu-button- {:href (bidi/path-for client-routes/routes
|
(menu-button- {:href (bidi/path-for client-routes/routes
|
||||||
:external-import-ledger)} "External Ledger Import"))
|
:external-import-ledger)} "External Ledger Import"))
|
||||||
|
|
||||||
(when (is-admin? (:identity request))
|
(when (is-admin? (:identity request))
|
||||||
(menu-button- {:href (hu/url (bidi/path-for ssr-routes/only-routes
|
(menu-button- {:href (hu/url (bidi/path-for ssr-routes/only-routes
|
||||||
::ledger-routes/external-import-page)
|
::ledger-routes/external-import-page)
|
||||||
|
|||||||
@@ -189,14 +189,14 @@
|
|||||||
|
|
||||||
|
|
||||||
(defn multi-typeahead- [params]
|
(defn multi-typeahead- [params]
|
||||||
[:div.relative {:x-data (doto (hx/json {:baseUrl (if (str/includes? (:url params) "?")
|
[:div.relative {:x-data (hx/json {:baseUrl (if (str/includes? (:url params) "?")
|
||||||
(str (:url params) "&q=")
|
(str (:url params) "&q=")
|
||||||
(str (:url params) "?q="))
|
(str (:url params) "?q="))
|
||||||
:reset_elements (js-fn "function(e) {
|
:reset_elements (js-fn "function(e) {
|
||||||
this.elements = [{value: 'all', label:'All'}].concat(e);
|
this.elements = [{value: 'all', label:'All'}].concat(e);
|
||||||
this.active = -1
|
this.active = -1
|
||||||
}")
|
}")
|
||||||
:toggle (js-fn "function(e) {
|
:toggle (js-fn "function(e) {
|
||||||
if (e.value == 'all') {
|
if (e.value == 'all') {
|
||||||
if (this.value.size > 0) {
|
if (this.value.size > 0) {
|
||||||
this.value = new Set([]);
|
this.value = new Set([]);
|
||||||
@@ -219,34 +219,33 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}")
|
}")
|
||||||
:all_selected (boolean (= (:value params) :all)),
|
:all_selected (boolean (= (:value params) :all)),
|
||||||
:value (cond
|
:value (cond
|
||||||
(= :all (:value params))
|
(= :all (:value params))
|
||||||
["all"]
|
["all"]
|
||||||
|
|
||||||
(sequential? (:value params))
|
(sequential? (:value params))
|
||||||
(map (fn [v] ((:value-fn params identity) v))
|
(map (fn [v] ((:value-fn params identity) v))
|
||||||
(:value params))
|
(:value params))
|
||||||
|
|
||||||
:else
|
:else
|
||||||
[])
|
[])
|
||||||
:tippy nil
|
:tippy nil
|
||||||
:lookup (into {}
|
:lookup (into {}
|
||||||
(when (sequential? (:value params))
|
(when (sequential? (:value params))
|
||||||
(map (fn [v] [((:value-fn params identity) v)
|
(map (fn [v] [((:value-fn params identity) v)
|
||||||
((:content-fn params identity) v)])
|
((:content-fn params identity) v)])
|
||||||
(:value params))))
|
(:value params))))
|
||||||
:x-init (str "$watch('value', v => $dispatch('change')); ")
|
:x-init (str "$watch('value', v => $dispatch('change')); ")
|
||||||
:search ""
|
:search ""
|
||||||
:active -1
|
:active -1
|
||||||
:elements (cond-> [{:value "all" :label "All"}]
|
:elements (cond-> [{:value "all" :label "All"}]
|
||||||
(sequential? (:value params))
|
(sequential? (:value params))
|
||||||
(into (map (fn [v]
|
(into (map (fn [v]
|
||||||
{:value ((:value-fn params identity) v)
|
{:value ((:value-fn params identity) v)
|
||||||
:label ((:content-fn params identity) v)})
|
:label ((:content-fn params identity) v)})
|
||||||
(:value params))))
|
(:value params))))
|
||||||
:x-ref "r"})
|
:x-ref "r"})
|
||||||
println)
|
|
||||||
;; :x-modelable "value.value" TODO
|
;; :x-modelable "value.value" TODO
|
||||||
;; :x-model (:x-model params) TODO
|
;; :x-model (:x-model params) TODO
|
||||||
:x-init "value=new Set(value || []); "}
|
:x-init "value=new Set(value || []); "}
|
||||||
@@ -351,7 +350,7 @@
|
|||||||
(update :class #(str % (use-size size) " w-full"))
|
(update :class #(str % (use-size size) " w-full"))
|
||||||
(dissoc :size))]])
|
(dissoc :size))]])
|
||||||
|
|
||||||
(defn multi-date-input- [{:keys [size] :as params}]
|
(defn multi-calendar-input- [{:keys [size] :as params}]
|
||||||
(let [value (str/join ", "
|
(let [value (str/join ", "
|
||||||
(for [v (:value params)
|
(for [v (:value params)
|
||||||
:when v]
|
:when v]
|
||||||
@@ -379,6 +378,30 @@
|
|||||||
(update :class #(str % (use-size size) " w-full"))
|
(update :class #(str % (use-size size) " w-full"))
|
||||||
(dissoc :size :name :x-model :x-modelable))]]))
|
(dissoc :size :name :x-model :x-modelable))]]))
|
||||||
|
|
||||||
|
(defn calendar-input- [{:keys [size] :as params}]
|
||||||
|
(let [value (:value params)]
|
||||||
|
[:div.shrink {:x-data (hx/json {:value value
|
||||||
|
:dp nil })
|
||||||
|
:x-modelable "value"
|
||||||
|
:x-model (:x-model params) }
|
||||||
|
[:input {:type "hidden" :name (:name params) :x-model "value"}]
|
||||||
|
[:div
|
||||||
|
(-> params
|
||||||
|
(update :class (fnil hh/add-class "") default-input-classes)
|
||||||
|
(assoc :type "text")
|
||||||
|
(assoc :value value)
|
||||||
|
;; the data-date field has to be bound before the datepicker can be initialized
|
||||||
|
(assoc :x-init "$nextTick(() => { dp = initCalendar($el); ;}); ")
|
||||||
|
(assoc "x-effect" "if(dp) { dp.setDate(value); } ")
|
||||||
|
(assoc ":data-date" "value")
|
||||||
|
(assoc "@htmx:before-history-save" "destroyDatepicker(dp)" )
|
||||||
|
(assoc "@htmx:before-cleanup-element" "destroyDatepicker(dp)" )
|
||||||
|
(assoc "x-destroy" "destroyDatepicker(dp)")
|
||||||
|
(assoc "@change-date.camel" "value = dp.getDate(\"mm/dd/yyyy\");")
|
||||||
|
|
||||||
|
(update :class #(str % (use-size size) " w-full"))
|
||||||
|
(dissoc :size :name :x-model :x-modelable))]]))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defn field-errors- [{:keys [source key]} & rest]
|
(defn field-errors- [{:keys [source key]} & rest]
|
||||||
|
|||||||
@@ -1,51 +1,51 @@
|
|||||||
(ns auto-ap.ssr.ledger
|
(ns auto-ap.ssr.ledger
|
||||||
(:require [auto-ap.datomic
|
(:require
|
||||||
|
[auto-ap.datomic
|
||||||
:refer [audit-transact audit-transact-batch conn pull-many
|
:refer [audit-transact audit-transact-batch conn pull-many
|
||||||
remove-nils]]
|
remove-nils]]
|
||||||
[auto-ap.datomic.accounts :as a]
|
[auto-ap.datomic.accounts :as a]
|
||||||
[auto-ap.graphql.utils :refer [assert-admin assert-can-see-client
|
[auto-ap.graphql.utils :refer [assert-admin assert-can-see-client
|
||||||
exception->notification
|
exception->notification notify-if-locked]]
|
||||||
notify-if-locked]]
|
[auto-ap.logging :as alog]
|
||||||
[auto-ap.logging :as alog]
|
[auto-ap.permissions :refer [wrap-must]]
|
||||||
[auto-ap.permissions :refer [wrap-must]]
|
[auto-ap.query-params :refer [wrap-copy-qp-pqp]]
|
||||||
[auto-ap.query-params :refer [wrap-copy-qp-pqp]]
|
[auto-ap.routes.ledger :as route]
|
||||||
[auto-ap.routes.ledger :as route]
|
[auto-ap.routes.utils
|
||||||
[auto-ap.routes.utils
|
|
||||||
:refer [wrap-client-redirect-unauthenticated]]
|
:refer [wrap-client-redirect-unauthenticated]]
|
||||||
[auto-ap.solr :as solr]
|
[auto-ap.solr :as solr]
|
||||||
[auto-ap.ssr-routes :as ssr-routes]
|
[auto-ap.ssr-routes :as ssr-routes]
|
||||||
[auto-ap.ssr.components :as com]
|
[auto-ap.ssr.components :as com]
|
||||||
[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]]
|
||||||
[auto-ap.ssr.hx :as hx]
|
[auto-ap.ssr.hx :as hx]
|
||||||
[auto-ap.ssr.ledger.balance-sheet :as balance-sheet]
|
[auto-ap.ssr.ledger.balance-sheet :as balance-sheet]
|
||||||
[auto-ap.ssr.ledger.common :refer [bank-account-filter
|
[auto-ap.ssr.ledger.cash-flows :as cash-flows]
|
||||||
default-read fetch-ids
|
[auto-ap.ssr.ledger.common :refer [bank-account-filter default-read
|
||||||
grid-page query-schema]]
|
fetch-ids grid-page query-schema]]
|
||||||
[auto-ap.ssr.ledger.investigate :as investigate]
|
[auto-ap.ssr.ledger.investigate :as investigate]
|
||||||
[auto-ap.ssr.nested-form-params :refer [wrap-nested-form-params]]
|
[auto-ap.ssr.nested-form-params :refer [wrap-nested-form-params]]
|
||||||
[auto-ap.ssr.svg :as svg]
|
[auto-ap.ssr.svg :as svg]
|
||||||
[auto-ap.ssr.ui :refer [base-page]]
|
[auto-ap.ssr.ui :refer [base-page]]
|
||||||
[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
|
||||||
html-response main-transformer money strip
|
html-response main-transformer money strip
|
||||||
wrap-form-4xx-2 wrap-implied-route-param
|
wrap-form-4xx-2 wrap-implied-route-param
|
||||||
wrap-merge-prior-hx wrap-schema-decode
|
wrap-merge-prior-hx wrap-schema-decode
|
||||||
wrap-schema-enforce]]
|
wrap-schema-enforce]]
|
||||||
[auto-ap.time :as atime]
|
[auto-ap.time :as atime]
|
||||||
[auto-ap.utils :refer [dollars=]]
|
[auto-ap.utils :refer [dollars=]]
|
||||||
[bidi.bidi :as bidi]
|
[bidi.bidi :as bidi]
|
||||||
[clj-time.coerce :as coerce]
|
[clj-time.coerce :as coerce]
|
||||||
[clj-time.core :as t]
|
[clj-time.core :as t]
|
||||||
[clojure.data.csv :as csv]
|
[clojure.data.csv :as csv]
|
||||||
[clojure.java.io :as io]
|
[clojure.java.io :as io]
|
||||||
[com.brunobonacci.mulog :as mu]
|
[clojure.string :as str]
|
||||||
[datomic.api :as dc]
|
[com.brunobonacci.mulog :as mu]
|
||||||
[hiccup2.core :as hiccup]
|
[datomic.api :as dc]
|
||||||
[iol-ion.utils :refer [by random-tempid]]
|
[hiccup2.core :as hiccup]
|
||||||
[malli.core :as mc]
|
[iol-ion.utils :refer [by random-tempid]]
|
||||||
[slingshot.slingshot :refer [throw+]]
|
[malli.core :as mc]
|
||||||
[clojure.string :as str]))
|
[slingshot.slingshot :refer [throw+]]))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -729,4 +729,5 @@
|
|||||||
(wrap-must {:activity :import :subject :ledger})
|
(wrap-must {:activity :import :subject :ledger})
|
||||||
(wrap-client-redirect-unauthenticated))))
|
(wrap-client-redirect-unauthenticated))))
|
||||||
balance-sheet/key->handler
|
balance-sheet/key->handler
|
||||||
|
cash-flows/key->handler
|
||||||
investigate/key->handler))
|
investigate/key->handler))
|
||||||
@@ -16,22 +16,21 @@
|
|||||||
[auto-ap.ssr.components :as com]
|
[auto-ap.ssr.components :as com]
|
||||||
[auto-ap.ssr.form-cursor :as fc]
|
[auto-ap.ssr.form-cursor :as fc]
|
||||||
[auto-ap.ssr.hx :as hx]
|
[auto-ap.ssr.hx :as hx]
|
||||||
|
[auto-ap.ssr.ledger.report-table :as rtable]
|
||||||
[auto-ap.ssr.svg :as svg]
|
[auto-ap.ssr.svg :as svg]
|
||||||
[auto-ap.ssr.ui :refer [base-page]]
|
[auto-ap.ssr.ui :refer [base-page]]
|
||||||
[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
|
||||||
html-response modal-response unspecified-transformer
|
html-response modal-response wrap-form-4xx-2
|
||||||
wrap-form-4xx-2 wrap-merge-prior-hx wrap-schema-enforce]]
|
wrap-schema-enforce]]
|
||||||
[auto-ap.time :as atime]
|
[auto-ap.time :as atime]
|
||||||
[bidi.bidi :as bidi]
|
[bidi.bidi :as bidi]
|
||||||
[clj-pdf.core :as pdf]
|
[clj-pdf.core :as pdf]
|
||||||
[clj-time.coerce :as coerce]
|
[clj-time.coerce :as coerce]
|
||||||
[clj-time.core :as t]
|
|
||||||
[clojure.java.io :as io]
|
[clojure.java.io :as io]
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[config.core :refer [env] :as env]
|
[config.core :refer [env] :as env]
|
||||||
[datomic.api :as dc]
|
[datomic.api :as dc]
|
||||||
[hiccup.util :as hu]
|
|
||||||
[iol-ion.utils :refer [by]]
|
[iol-ion.utils :refer [by]]
|
||||||
[malli.core :as mc])
|
[malli.core :as mc])
|
||||||
(:import
|
(:import
|
||||||
@@ -40,29 +39,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
(def default-read
|
|
||||||
'[:journal-entry/amount
|
|
||||||
:journal-entry/alternate-description
|
|
||||||
:journal-entry/source
|
|
||||||
:journal-entry/external-id
|
|
||||||
:db/id
|
|
||||||
[:journal-entry/date :xform clj-time.coerce/from-date]
|
|
||||||
{:journal-entry/vendor [:vendor/name :db/id]
|
|
||||||
:journal-entry/original-entity [:invoice/invoice-number
|
|
||||||
:invoice/source-url
|
|
||||||
:transaction/description-original :db/id]
|
|
||||||
:journal-entry/client [:client/name :client/code :db/id]
|
|
||||||
:journal-entry/line-items [:journal-entry-line/debit
|
|
||||||
:journal-entry-line/location
|
|
||||||
:journal-entry-line/running-balance
|
|
||||||
:journal-entry-line/credit
|
|
||||||
{:journal-entry-line/account [:account/name :db/id :account/numeric-code
|
|
||||||
:bank-account/name :bank-account/numeric-code
|
|
||||||
{[:account/type :xform iol-ion.query/ident] [:db/ident :db/id]}
|
|
||||||
{:account/client-overrides [:account-client-override/name
|
|
||||||
{:account-client-override/client [:db/id]}]}
|
|
||||||
{[:bank-account/type :xform iol-ion.query/ident]
|
|
||||||
[:db/ident :db/id]}]}]}])
|
|
||||||
|
|
||||||
|
|
||||||
(def query-schema (mc/schema
|
(def query-schema (mc/schema
|
||||||
@@ -78,126 +54,6 @@
|
|||||||
:decode/string (fn [s] (if (string? s) (str/split s #", ")
|
:decode/string (fn [s] (if (string? s) (str/split s #", ")
|
||||||
s))}
|
s))}
|
||||||
clj-date-schema]] ]]))
|
clj-date-schema]] ]]))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defn cell [{:keys [width investigate-url other-style]} c]
|
|
||||||
(let [cell-contents (cond
|
|
||||||
|
|
||||||
(= :dollar (:format c))
|
|
||||||
(format "$%,.2f" (if (iol-ion.query/dollars-0? (:value c))
|
|
||||||
0.0
|
|
||||||
(:value c)))
|
|
||||||
|
|
||||||
|
|
||||||
(= :percent (:format c))
|
|
||||||
(format "%%%.1f" (if (iol-ion.query/dollars-0? (:value c))
|
|
||||||
0.0
|
|
||||||
(:value c)))
|
|
||||||
|
|
||||||
:else
|
|
||||||
(str (:value c)))
|
|
||||||
cell-contents (if (:filters c)
|
|
||||||
(com/link {:hx-get (hu/url investigate-url
|
|
||||||
(cond-> {}
|
|
||||||
(:numeric-code (:filters c)) (assoc :numeric-code (into [] (:numeric-code (:filters c))))
|
|
||||||
;; TODO
|
|
||||||
#_#_(:date-range (:filters c)) (assoc :end-date (atime/unparse-local (:date-range (:filters c))
|
|
||||||
atime/normal-date))
|
|
||||||
(:client-id (:filters c)) (assoc :client-id (:client-id (:filters c))))
|
|
||||||
)}
|
|
||||||
cell-contents)
|
|
||||||
cell-contents)]
|
|
||||||
[:td.px-4.py-2
|
|
||||||
(cond-> {:style (cond-> {:width (str width "em")}
|
|
||||||
other-style (merge other-style))}
|
|
||||||
|
|
||||||
(:border c) (update :style
|
|
||||||
(fn [s]
|
|
||||||
(->> (:border c)
|
|
||||||
(map
|
|
||||||
(fn [b]
|
|
||||||
[(keyword (str "border-" (name b))) "1px solid black"])
|
|
||||||
)
|
|
||||||
(into s))))
|
|
||||||
(:colspan c) (assoc :col-span (:colspan c))
|
|
||||||
(:align c) (assoc :align (:align c))
|
|
||||||
(= :dollar (:format c)) (assoc :align :right)
|
|
||||||
(= :percent (:format c)) (assoc :align :right)
|
|
||||||
(:bold c) (assoc-in [:style :font-weight] "bold")
|
|
||||||
(:color c) (assoc-in [:style :color] (str "rgb("
|
|
||||||
(str/join ","
|
|
||||||
(:color c))
|
|
||||||
")"))
|
|
||||||
true (assoc-in [:style :background-color] (str "rgb("
|
|
||||||
(str/join ","
|
|
||||||
(or (:bg-color c) [255 255 255]))
|
|
||||||
")")))
|
|
||||||
|
|
||||||
cell-contents]))
|
|
||||||
|
|
||||||
(defn cell-count [table]
|
|
||||||
(let [counts (map count (:rows table))]
|
|
||||||
(if (seq counts)
|
|
||||||
(apply max counts)
|
|
||||||
0)))
|
|
||||||
|
|
||||||
(defn table [{:keys [table widths investigate-url warning]}]
|
|
||||||
(let [cell-count (cell-count table)]
|
|
||||||
(com/content-card {:class "inline-block overflow-scroll"}
|
|
||||||
[:div {:class "overflow-scroll h-[70vh] m-4 inline-block"}
|
|
||||||
(when warning [:div.rounded.bg-red-50.text-red-800.p-4.m-2
|
|
||||||
warning])
|
|
||||||
(-> [:table {:class "text-sm text-left text-gray-500 dark:text-gray-400"}
|
|
||||||
[:thead {:class "text-xs text-gray-800 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400 font-bold"}
|
|
||||||
(map
|
|
||||||
(fn [header-row header]
|
|
||||||
(into
|
|
||||||
[:tr {:class " dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700"}]
|
|
||||||
(map
|
|
||||||
(fn [w header i]
|
|
||||||
(cell {:width w
|
|
||||||
:investigate-url investigate-url
|
|
||||||
:other-style {:position "sticky"
|
|
||||||
:top (* header-row (+ 22 18))}} header))
|
|
||||||
widths
|
|
||||||
header
|
|
||||||
(range))))
|
|
||||||
(range)
|
|
||||||
(:header table))]]
|
|
||||||
|
|
||||||
(conj
|
|
||||||
(-> [:tbody {:style {}}]
|
|
||||||
(into
|
|
||||||
(for [[i row] (map vector (range) (:rows table))]
|
|
||||||
|
|
||||||
[:tr {:class " dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-700"}
|
|
||||||
(for [[i c] (map vector (range) (take cell-count
|
|
||||||
(reduce
|
|
||||||
(fn [[acc cnt] cur]
|
|
||||||
(if (>= (+ cnt (:colspan cur 1)) cell-count)
|
|
||||||
(reduced (conj acc cur))
|
|
||||||
[(conj acc cur) (+ cnt (:colspan cur 1))]))
|
|
||||||
[[] 0]
|
|
||||||
(concat row (repeat nil)))))]
|
|
||||||
|
|
||||||
(cell {:investigate-url investigate-url} c))]))
|
|
||||||
(conj [:tr (for [i (range cell-count)]
|
|
||||||
|
|
||||||
(cell {:investigate-url investigate-url} {:value " "}))]))))])))
|
|
||||||
|
|
||||||
(defn concat-tables [tables]
|
|
||||||
(let [[first & rest] tables]
|
|
||||||
{:header (:header first)
|
|
||||||
:rows (concat (:rows first)
|
|
||||||
[[]]
|
|
||||||
(mapcat
|
|
||||||
(fn [table]
|
|
||||||
(-> (:header table)
|
|
||||||
(into (:rows table))
|
|
||||||
(conj [])))
|
|
||||||
rest))}))
|
|
||||||
|
|
||||||
;; TODO
|
;; TODO
|
||||||
;; 1. Rerender form when running
|
;; 1. Rerender form when running
|
||||||
;; 2. Don't throw crazy errors when missing a field
|
;; 2. Don't throw crazy errors when missing a field
|
||||||
@@ -257,7 +113,7 @@
|
|||||||
client-count (count (set (map :client-id (:data data)))) ]
|
client-count (count (set (map :client-id (:data data)))) ]
|
||||||
(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))) ]
|
||||||
(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))))))
|
||||||
: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
|
||||||
@@ -314,7 +170,7 @@
|
|||||||
[:template {:x-ref "tooltip"}
|
[:template {:x-ref "tooltip"}
|
||||||
[:div.p-4 {:class "bg-gray-100 dark:bg-gray-600 rounded-lg shadow-2xl w-max z-50 ring-1 p-4"}
|
[:div.p-4 {:class "bg-gray-100 dark:bg-gray-600 rounded-lg shadow-2xl w-max z-50 ring-1 p-4"}
|
||||||
[:div.flex.flex-col.gap-2
|
[:div.flex.flex-col.gap-2
|
||||||
(com/multi-date-input {:placeholder "12/21/2020"
|
(com/multi-calendar-input {:placeholder "12/21/2020"
|
||||||
:x-model "dates" })
|
:x-model "dates" })
|
||||||
(com/a-button {"@click" "dates=getFourWeekPeriods(dates[dates.length -1])"} "13 periods")
|
(com/a-button {"@click" "dates=getFourWeekPeriods(dates[dates.length -1])"} "13 periods")
|
||||||
(com/a-button {"@click" "dates=withLastYear(dates[dates.length -1])"} "Add prior year")
|
(com/a-button {"@click" "dates=withLastYear(dates[dates.length -1])"} "Add prior year")
|
||||||
@@ -386,7 +242,6 @@
|
|||||||
name (balance-sheet-args->name request)
|
name (balance-sheet-args->name request)
|
||||||
key (str "reports/balance-sheet/" uuid "/" name ".pdf")
|
key (str "reports/balance-sheet/" uuid "/" name ".pdf")
|
||||||
url (str "https://" (:data-bucket env) "/" key)]
|
url (str "https://" (:data-bucket env) "/" key)]
|
||||||
(println "CLIENT IS" client)
|
|
||||||
(s3/put-object :bucket-name (:data-bucket env/env)
|
(s3/put-object :bucket-name (:data-bucket env/env)
|
||||||
:key key
|
:key key
|
||||||
:input-stream (io/make-input-stream pdf-data {})
|
:input-stream (io/make-input-stream pdf-data {})
|
||||||
|
|||||||
@@ -49,6 +49,7 @@
|
|||||||
[:link {:rel "stylesheet" :href "https://cdn.jsdelivr.net/npm/vanillajs-datepicker@1.1.4/dist/css/datepicker.min.css"}]
|
[:link {:rel "stylesheet" :href "https://cdn.jsdelivr.net/npm/vanillajs-datepicker@1.1.4/dist/css/datepicker.min.css"}]
|
||||||
[:script {:type "text/javascript" :src "https://cdn.jsdelivr.net/npm/vanillajs-datepicker@1.1.4/dist/js/datepicker-full.min.js"}]
|
[:script {:type "text/javascript" :src "https://cdn.jsdelivr.net/npm/vanillajs-datepicker@1.1.4/dist/js/datepicker-full.min.js"}]
|
||||||
[:script {:src "https://unpkg.com/htmx.org/dist/ext/response-targets.js"}]
|
[:script {:src "https://unpkg.com/htmx.org/dist/ext/response-targets.js"}]
|
||||||
|
[:script {:src "https://cdn.jsdelivr.net/npm/date-fns@3.6.0/cdn.min.js" :defer true}]
|
||||||
[:script {:src "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js" :integrity "sha512-CQBWl4fJHWbryGE+Pc7UAxWMUMNMWzWxF4SQo9CgkJIN1kx6djDQZjh3Y8SZ1d+6I+1zze6Z7kHXO7q3UyZAWw==" :crossorigin "anonymous" :referrerpolicy "no-referrer"}]
|
[:script {:src "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js" :integrity "sha512-CQBWl4fJHWbryGE+Pc7UAxWMUMNMWzWxF4SQo9CgkJIN1kx6djDQZjh3Y8SZ1d+6I+1zze6Z7kHXO7q3UyZAWw==" :crossorigin "anonymous" :referrerpolicy "no-referrer"}]
|
||||||
|
|
||||||
[:script {:src "https://unpkg.com/dropzone@5.9.3/dist/min/dropzone.min.js"}]
|
[:script {:src "https://unpkg.com/dropzone@5.9.3/dist/min/dropzone.min.js"}]
|
||||||
|
|||||||
@@ -776,6 +776,7 @@
|
|||||||
(let [client-ids (->> (client-locations pnl-data)
|
(let [client-ids (->> (client-locations pnl-data)
|
||||||
(map first)
|
(map first)
|
||||||
set)]
|
set)]
|
||||||
|
|
||||||
{:warning (warning-message pnl-data)
|
{:warning (warning-message pnl-data)
|
||||||
:details
|
:details
|
||||||
(doall (for [client-id client-ids]
|
(doall (for [client-id client-ids]
|
||||||
|
|||||||
@@ -11,4 +11,7 @@
|
|||||||
"/bank-account-filter" ::bank-account-filter
|
"/bank-account-filter" ::bank-account-filter
|
||||||
"/reports/balance-sheet" {"" ::balance-sheet
|
"/reports/balance-sheet" {"" ::balance-sheet
|
||||||
"/run" ::run-balance-sheet
|
"/run" ::run-balance-sheet
|
||||||
"/export" ::export-balance-sheet}})
|
"/export" ::export-balance-sheet}
|
||||||
|
"/reports/cash-flows" {"" ::cash-flows
|
||||||
|
"/run" ::run-cash-flows
|
||||||
|
"/export" ::export-cash-flows}})
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
[(keyword (str "border-" (name b))) "1px solid black"])
|
[(keyword (str "border-" (name b))) "1px solid black"])
|
||||||
)
|
)
|
||||||
(into s))))
|
(into s))))
|
||||||
(:colspan c) (assoc :col-span (:colspan c))
|
(:colspan c) (assoc :colspan (:colspan c))
|
||||||
(:align c) (assoc :align (:align c))
|
(:align c) (assoc :align (:align c))
|
||||||
(= :dollar (:format c)) (assoc :align :right)
|
(= :dollar (:format c)) (assoc :align :right)
|
||||||
(= :percent (:format c)) (assoc :align :right)
|
(= :percent (:format c)) (assoc :align :right)
|
||||||
|
|||||||
Reference in New Issue
Block a user