fully removes old invoice experience

This commit is contained in:
2024-08-09 20:32:32 -07:00
parent e947b4592c
commit 1bb8387051
21 changed files with 268 additions and 2333 deletions

View File

@@ -2,13 +2,13 @@
(ns auto-ap.ssr.invoice.glimpse
(:require [amazonica.aws.s3 :as s3]
[amazonica.aws.textract :as textract]
[auto-ap.client-routes :as client-routes]
[auto-ap.datomic :refer [conn pull-attr]]
[auto-ap.datomic.clients :as d-clients]
[auto-ap.datomic.vendors :as d-vendors]
[auto-ap.datomic.invoices :as d-invoices]
[auto-ap.datomic.vendors :as d-vendors]
[auto-ap.graphql.utils :refer [extract-client-ids]]
[auto-ap.logging :as alog]
[auto-ap.routes.invoice :as i-route]
[auto-ap.solr :as solr]
[auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.components :as com]
@@ -443,8 +443,8 @@ invoice_dropzone = new Dropzone(\"#invoice\", {
:headers {"hx-push-url" (bidi/path-for ssr-routes/only-routes :invoice-glimpse)
"hx-retarget" "#invoice-glimpse-content"
"hx-trigger" (cheshire/generate-string {"notification" (str (hiccup/html [:div "Successfully created "
(com/link {:href (str (bidi/path-for client-routes/routes
:invoices)
(com/link {:href (str (bidi/path-for ssr-routes/only-routes
::i-route/all-page)
"?exact-match-id="
new-invoice-id)}
(format "invoice %s" (:invoice/invoice-number new-invoice)))

View File

@@ -339,7 +339,7 @@
[:div.flex.justify-between.items-center [:h1.text-2xl.mb-3.font-bold "Import new invoices"]
[:div.flex.gap-2.items-baseline "Trouble with the new upload experience?"
#_[:div.flex.gap-2.items-baseline "Trouble with the new upload experience?"
[:a {:href (bidi/path-for client-routes/routes :import-invoices)}
(com/pill {:color :secondary}
"Go back to previous version")]]]

View File

@@ -1266,6 +1266,12 @@
[]
{:mode :simple
:has-warning? (boolean has-warning?)}))))
(defn redirect-handler [target-route]
(fn handle [request]
{:status 302
:headers {"Location" (str (hu/url (bidi.bidi/path-for ssr-routes/only-routes
target-route)
(:query-params request)))}}))
(def key->handler
(apply-middleware-to-all-handlers
@@ -1278,6 +1284,12 @@
(wrap-implied-route-param :status :invoice-status/unpaid))
::route/voided-page (-> (helper/page-route grid-page :parse-query-params? false)
(wrap-implied-route-param :status :invoice-status/voided))
::route/legacy-invoices (redirect-handler ::route/all-page)
::route/legacy-import-invoices (redirect-handler ::route/import-page)
::route/legacy-unpaid-invoices (redirect-handler ::route/unpaid-page)
::route/legacy-paid-invoices (redirect-handler ::route/paid-page)
::route/legacy-voided-invoices (redirect-handler ::route/voided-page)
::route/legacy-new-invoice (redirect-handler ::route/new-wizard)
::route/unvoid (-> unvoid-invoice
(wrap-entity [:route-params :db/id] default-read)
(wrap-schema-enforce :route-params [:map [:db/id entity-id]]))

View File

@@ -468,3 +468,236 @@
(dc/pull (dc/db conn) '[* {:bank-account/_plaid-account [:bank-account/code]}] 17592310327452)
(seq (dc/q '[:find ?ns
:in $ $$ ?log
:where
[$$ ?x :audit/user "admin-Ashlee Phillips"]
[(tx-data ?log ?x) [[?e ?a ?v _ ?added]]]
[?a :db/ident ?a2]
[(namespace ?a2) ?ns]
]
(dc/db conn)
(dc/history (dc/since (dc/db conn) #inst "2024-06-15T00:00:00-08:00"))
(dc/log conn)))
(dc/q '[:find ?d2 ?ns ?code ?a2 ?added (count ?x)
:in $ $$ ?log
:where
[$$ ?x :audit/user "admin-Ashlee Phillips"]
[(tx-data ?log ?x) [[?e ?a ?v _ ?added]]]
[(get-some $$ ?e :transaction/client :invoice/client :payment/client :journal-entry/client) [?attr ?c]]
[?c :client/code ?code]
[?a :db/ident ?a2]
[(namespace ?a2) ?ns]
[?x :db/txInstant ?d]
[(iol-ion.query/excel-date ?d) ?d2]]
(dc/db conn)
(dc/history (dc/since (dc/db conn) #inst "2024-07-01T00:00:00-08:00"))
(dc/log conn))
(defn join-attrs [xs]
(clojure.string/join ", " (sort xs)))
(comment
"Behaviors in time region"
(clojure.data.csv/write-csv *out*
(concat
(->> (dc/q '[:find ?d2 ?code ?entity ?e ?desc (user/join-attrs ?attr)
:in $$$ $$ $ ?log
:where
[$$ ?x :audit/user "admin-Ashlee Phillips"]
[(tx-data ?log ?x) [[?e ?a ?v _ ?added]]]
($$$ or-join [?e ?c]
[?c :client/bank-accounts ?e]
[?e :account-client-override/client ?c]
[?e :journal-entry/client ?c]
(and [?t :transaction/accounts ?e]
[?t :transaction/client ?c])
(and [?e :client/code]
[?e ?c])
(and [?je :journal-entry/line-items ?e]
[?je :journal-entry/client ?c])
[?e :payment/client ?c]
[?e :invoice/client ?c]
(and [?i :invoice/expense-accounts ?e]
[?i :invoice/client ?c])
[?e :transaction/client ?c]
(and [?e :invoice-payment/invoice ?i]
[?i :invoice/client ?c])
#_[(ground 0) ?c])
[(get-else $ ?c :client/code "") ?code]
[?a :db/ident ?a2]
[(namespace ?a2) ?entity]
[(name ?a2) ?attr]
#_[(= ?a3 "account-client-override")]
[?x :db/txInstant ?d]
[(iol-ion.query/excel-date ?d) ?d2]
[(if ?added "changed" "replaced/deleted") ?desc]]
(dc/history (dc/db conn))
(dc/history (dc/since (dc/db conn) #inst "2024-06-15T00:00:00-08:00"))
(dc/db conn)
(dc/log conn))
seq)
(->> (dc/q '[:find ?d2 ?code ?entity ?e ?desc (user/join-attrs ?attr)
:in $$$ $$ $ ?log
:where
[$$ ?x :audit/user "admin-Ashlee Phillips"]
[(tx-data ?log ?x) [[?e ?a ?v _ ?added]]]
[(ground "") ?code]
[?a :db/ident ?a2]
[(namespace ?a2) ?entity]
(not
[(get #{"invoice" "transaction" "account-client-override" "journal-entry"
"journal-entry-line" "bank-account" "payment" "invoice-payment"
"client" "invoice-expense-account" "transaction-account"} ?entity)])
[(name ?a2) ?attr]
#_[(= ?a3 "account-client-override")]
[?x :db/txInstant ?d]
[(iol-ion.query/excel-date ?d) ?d2]
[(if ?added "changed" "replaced/deleted") ?desc]]
(dc/history (dc/db conn))
(dc/history (dc/since (dc/db conn) #inst "2024-06-15T00:00:00-08:00"))
(dc/db conn)
(dc/log conn))
seq))
:separator \tab)
)
(clojure.data.csv/write-csv *out*
(concat
(->> (dc/q '[:find ?d2 ?code ?entity (count ?e)
:in $$$ $$ $ ?log
:where
[$$ ?x :audit/user "admin-Ashlee Phillips"]
[(tx-data ?log ?x) [[?e ?a ?v _ ?added]]]
($$$ or-join [?e ?c]
[?c :client/bank-accounts ?e]
[?e :account-client-override/client ?c]
[?e :journal-entry/client ?c]
(and [?t :transaction/accounts ?e]
[?t :transaction/client ?c])
(and [?e :client/code]
[?e ?c])
(and [?je :journal-entry/line-items ?e]
[?je :journal-entry/client ?c])
[?e :payment/client ?c]
[?e :invoice/client ?c]
(and [?i :invoice/expense-accounts ?e]
[?i :invoice/client ?c])
[?e :transaction/client ?c]
(and [?e :invoice-payment/invoice ?i]
[?i :invoice/client ?c])
#_[(ground 0) ?c])
[(get-else $ ?c :client/code "") ?code]
[?a :db/ident ?a2]
[(namespace ?a2) ?entity]
[(name ?a2) ?attr]
#_[(= ?a3 "account-client-override")]
[?x :db/txInstant ?d]
[(iol-ion.query/excel-date ?d) ?d2]
[(if ?added "changed" "replaced/deleted") ?desc]]
(dc/history (dc/db conn))
(dc/history (dc/since (dc/db conn) #inst "2024-06-15T00:00:00-08:00"))
(dc/db conn)
(dc/log conn))
seq)
(->> (dc/q '[:find ?d2 ?code ?entity (count ?e)
:in $$$ $$ $ ?log
:where
[$$ ?x :audit/user "admin-Ashlee Phillips"]
[(tx-data ?log ?x) [[?e ?a ?v _ ?added]]]
[(ground "") ?code]
[?a :db/ident ?a2]
[(namespace ?a2) ?entity]
(not
[(get #{"invoice" "transaction" "account-client-override" "journal-entry"
"journal-entry-line" "bank-account" "payment" "invoice-payment"
"client" "invoice-expense-account" "transaction-account"} ?entity)])
[(name ?a2) ?attr]
#_[(= ?a3 "account-client-override")]
[?x :db/txInstant ?d]
[(iol-ion.query/excel-date ?d) ?d2]
[(if ?added "changed" "replaced/deleted") ?desc]]
(dc/history (dc/db conn))
(dc/history (dc/since (dc/db conn) #inst "2024-06-15T00:00:00-08:00"))
(dc/db conn)
(dc/log conn))
seq))
:separator \tab)
(clojure.data.csv/write-csv *out*
(concat
(->> (dc/q '[:find ?code ?entity (count ?e)
:in $$$ $$ $ ?log
:where
[$$ ?x :audit/user "admin-Ashlee Phillips"]
[(tx-data ?log ?x) [[?e ?a ?v _ ?added]]]
($$$ or-join [?e ?c]
[?c :client/bank-accounts ?e]
[?e :account-client-override/client ?c]
[?e :journal-entry/client ?c]
(and [?t :transaction/accounts ?e]
[?t :transaction/client ?c])
(and [?e :client/code]
[?e ?c])
(and [?je :journal-entry/line-items ?e]
[?je :journal-entry/client ?c])
[?e :payment/client ?c]
[?e :invoice/client ?c]
(and [?i :invoice/expense-accounts ?e]
[?i :invoice/client ?c])
[?e :transaction/client ?c]
(and [?e :invoice-payment/invoice ?i]
[?i :invoice/client ?c])
#_[(ground 0) ?c])
[(get-else $ ?c :client/code "") ?code]
[?a :db/ident ?a2]
[(namespace ?a2) ?entity]
[(name ?a2) ?attr]
#_[(= ?a3 "account-client-override")]
[?x :db/txInstant ?d]
[(iol-ion.query/excel-date ?d) ?d2]
[(if ?added "changed" "replaced/deleted") ?desc]]
(dc/history (dc/db conn))
(dc/history (dc/since (dc/db conn) #inst "2024-06-15T00:00:00-08:00"))
(dc/db conn)
(dc/log conn))
seq)
(->> (dc/q '[:find ?code ?entity (count ?e)
:in $$$ $$ $ ?log
:where
[$$ ?x :audit/user "admin-Ashlee Phillips"]
[(tx-data ?log ?x) [[?e ?a ?v _ ?added]]]
[(ground "") ?code]
[?a :db/ident ?a2]
[(namespace ?a2) ?entity]
(not
[(get #{"invoice" "transaction" "account-client-override" "journal-entry"
"journal-entry-line" "bank-account" "payment" "invoice-payment"
"client" "invoice-expense-account" "transaction-account"} ?entity)])
[(name ?a2) ?attr]
#_[(= ?a3 "account-client-override")]
[?x :db/txInstant ?d]
[(iol-ion.query/excel-date ?d) ?d2]
[(if ?added "changed" "replaced/deleted") ?desc]]
(dc/history (dc/db conn))
(dc/history (dc/since (dc/db conn) #inst "2024-06-15T00:00:00-08:00"))
(dc/db conn)
(dc/log conn))
seq))
:separator \tab)
(dc/q '[:find ?c
:in $ [?e ...]
:where (or-join [?e ?c]
[?e :invoice/client ?c]
[?e :transaction/client ?c])]
(dc/db conn)
[17592296942460])
1

View File

@@ -8,12 +8,7 @@
"payments/" :payments
"admin/" {"vendors" :admin-vendors}
"vendor/" {"new" :new-vendor}
"invoices/" {"" :invoices
"import" :import-invoices
"unpaid" :unpaid-invoices
"paid" :paid-invoices
"voided" :voided-invoices
"new" :new-invoice}
"transactions/" {"" :transactions
"unapproved" :unapproved-transactions
"approved" :approved-transactions

View File

@@ -38,3 +38,10 @@
["/" [#"\w+" :textract-invoice-id]] {:get :invoice-glimpse-textract-invoice
"/create" {:post :invoice-glimpse-create-invoice}
"/update" {:patch :invoice-glimpse-update-textract-invoice}}}}})
(def legacy-routes {"" ::legacy-invoices
"import" ::legacy-import-invoices
"unpaid" ::legacy-unpaid-invoices
"paid" ::legacy-paid-invoices
"voided" ::legacy-voided-invoices
"new" ::legacy-new-invoice})

View File

@@ -16,7 +16,7 @@
"logout" :logout
"search" :search
"indicators" indicator-routes/routes
"dashboard" d-routes/routes
"account" {"/search" {:get :account-search}}
"admin" {"" :auto-ap.routes.admin/page
@@ -66,12 +66,15 @@
"/table" {:get :pos-refund-table}}
"/cash-drawer-shifts" {"" {:get :pos-cash-drawer-shifts}
"/table" {:get :pos-cash-drawer-shift-table}}}
"outgoing-invoice" oi-routes/routes
"payment" p-routes/routes
"invoice" i-routes/routes
"invoices/" i-routes/legacy-routes
"invoices" i-routes/legacy-routes
"vendor" {"/search" :vendor-search}
;; TODO Include IDS in routes for company-specific things, as opposed to headers
;; TODO Include IDS in routes for company-specific things, as opposed to headers
"company" {"" :company
"/dropdown" :company-dropdown-search-results
"/signature" {"/put" :company-update-signature
@@ -99,7 +102,7 @@
"/fastlink" {:get :company-yodlee-fastlink-dialog}
"/refresh" {:put :company-yodlee-provider-account-refresh}
"/reauthenticate" {:put :company-yodlee-provider-account-reauthenticate}}
"/plaid" {"" {:get :company-plaid}
"/table" {:get :company-plaid-table}
"/link" {:post :company-plaid-link}

View File

@@ -1,187 +0,0 @@
(ns auto-ap.views.components.expense-accounts-dialog
(:require
[auto-ap.forms :as forms]
[auto-ap.status :as status]
[auto-ap.utils :refer [by]]
[auto-ap.views.components.modal :as modal]
[auto-ap.views.components.typeahead.vendor :refer [search-backed-typeahead]]
[auto-ap.views.pages.invoices.common :refer [invoice-read]]
[auto-ap.views.utils :refer [dispatch-event with-user]]
[clojure.string :as str]
[goog.string :as gstring]
[re-frame.core :as re-frame]
[auto-ap.forms.builder :as form-builder]
[auto-ap.views.components :as com]))
(re-frame/reg-event-fx
::try-save
[(forms/in-form ::form)]
(fn [{:keys [db]} _]
(let [{{:keys [ total]} :invoice
:keys [expense-accounts]} (:data db)
expense-accounts (vals expense-accounts)
expense-accounts-total (->> expense-accounts
(map :new-amount)
(map js/parseFloat)
(map #(or % 0.0))
(map #(if (js/Number.isNaN %)
0.0
%))
(reduce + 0))
does-add-up? (< (Math/abs (- expense-accounts-total (js/parseFloat total))) 0.001)]
(if (and does-add-up?
(every? :new-amount expense-accounts))
{:dispatch [::save]}
{:dispatch [::status/error ::form [{:message "Expense accounts do not add up."}]]}))))
(re-frame/reg-event-fx
::save
[with-user (forms/in-form ::form)]
(fn [{:keys [db user] } _]
(let [{{:keys [id]} :invoice
:keys [expense-accounts]} (:data db)
expense-accounts (vals expense-accounts)]
{:graphql
{:token user
:owns-state {:single ::form}
:query-obj {:venia/operation {:operation/type :mutation
:operation/name "EditExpenseAccounts"}
:venia/queries [{:query/data [:edit-expense-accounts
{:invoice-id id
:expense-accounts (map (fn [ea] {:id (if (clojure.string/includes? (str (:id ea)) "new-")
nil
(:id ea))
:amount (:new-amount ea)
:location (:location ea)
:account-id (:id (:account ea))})
expense-accounts)}
invoice-read]}]}
:on-success (fn [result]
[::updated (:edit-expense-accounts result)])}})))
(re-frame/reg-event-fx
::updated
(fn [_ _]
{:dispatch [::modal/modal-closed]}))
(re-frame/reg-event-db
::add-split
[(forms/in-form ::form)]
(fn [db _]
(update-in db [:data :expense-accounts]
assoc (str "new-" (random-uuid)) {:amount "0.0" :account nil :location (-> db :data :invoice :client :locations first) })))
(re-frame/reg-event-db
::remove-expense-account-split
[(forms/in-form ::form)]
(fn [db [_ x]]
(update-in db [:data :expense-accounts] dissoc x)))
(defn form []
(let [{:keys [data]} @(re-frame/subscribe [::forms/form ::form])
expense-accounts (:expense-accounts data)
{:keys [total] :or {total 0} {:keys [locations] :as client} :client} (:invoice data)
multi-location? (> (count locations) 1)
expense-accounts-total (->> expense-accounts
vals
(map :new-amount)
(map js/parseFloat)
(map #(or % 0.0))
(map #(if (js/Number.isNaN %)
0.0
%))
(reduce + 0))]
[:div
[:div
[:a.button.is-outlined {:on-click (dispatch-event [::add-split])} "Add split"]]
[form-builder/builder {:submit-event [::try-save]
:id ::form}
[:table.table
[:thead
[:tr
[:th {:style {:width "500px"}} "Expense Account"]
(when multi-location?
[:th {:style {:width "200px"}} "Location"])
[:th {:style {:width "200px"}} "Original Amount"]
[:th {:style {:width "300px"}} "Amount"]
[:th {:style {:width "5em"}}]]]
[:tbody
(doall (for [[id a] expense-accounts]
^{:key id}
[:tr {:class (when (:warning (:account a))
"has-background-warning-light")}
[:td.expandable [:div.control
[form-builder/raw-field-v2 {:field [:expense-accounts id :account]}
[search-backed-typeahead {:search-query (fn [i]
[:search_account
{:query i
:client-id (:id client)
:vendor-id (:id (:vendor (:invoice data)))
:allowance :invoice}
[:name :id :location :warning]])
:class "is-warn"}]]]]
(when multi-location?
[:td
(if-let [forced-location (get-in expense-accounts [id :account :location])]
[:div.select
[:select {:disabled "disabled" :value forced-location} [:option {:value forced-location} forced-location]]]
[:div.select
[form-builder/raw-field-v2 {:field [:expense-accounts id :location]}
[com/select-field {:options (map (fn [l] [l l])
locations)
:allow-nil? true}]]])])
[:td
(str "$" (get-in expense-accounts [id :amount]))]
[:td
[:div.control
[:div.field.has-addons.is-extended
[:p.control [:a.button.is-static "$"]]
[:p.control
[form-builder/raw-field-v2 {:field [:expense-accounts id :new-amount-temp]}
[:input.input {:type "number"
:style {:text-align "right"}
:on-blur (dispatch-event [::forms/change ::form [:expense-accounts id :new-amount] (get-in expense-accounts [id :new-amount-temp])])
:on-key-down (fn [e ]
(if (= 13 (.-keyCode e))
(do
(re-frame/dispatch [::forms/change ::form [:expense-accounts id :new-amount] (get-in expense-accounts [id :new-amount-temp])])
true)
false))
:max (:total data)
:step "0.01"}]]]]]]
[:td [:a.button {:on-click (dispatch-event [::remove-expense-account-split id])} [:i.fa.fa-times]]]]))
[:tr
[:td.no-border { :col-span (if multi-location? "3" "2") :style { :text-align "right"} } "Invoice total: "]
[:td.no-border { :style { :text-align "right"} } (str (gstring/format "$%.2f" total ) )]]
[:tr
[:td { :col-span (if multi-location? "3" "2") :style { :text-align "right"} } "Account total: "]
[:td { :style { :text-align "right"} } (str (gstring/format "$%.2f" expense-accounts-total ) )]]
[:tr
[:td.no-border { :col-span (if multi-location? "3" "2") :style { :text-align "right"} } "Difference: "]
[:td.no-border { :style { :text-align "right"} } (str (gstring/format "$%.2f" (- total expense-accounts-total) ) )]]]]
[form-builder/hidden-submit-button]]]))
(re-frame/reg-event-fx
::show
(fn [{:keys [db]} [_ i]]
{:dispatch [::modal/modal-requested {:title "Change expense accounts"
:body [form]
:confirm {:value "Save"
:status-from [::status/single ::form]
:class "is-primary"
:on-click (dispatch-event [::try-save])
:close-event [::status/completed ::form]}}]
:db (-> db
(forms/start-form ::form
{:expense-accounts (by :id
(:expense-accounts i))
:invoice i}))}))

View File

@@ -1,302 +0,0 @@
(ns auto-ap.views.components.invoice-table
(:require [auto-ap.events :as events]
[auto-ap.routes :as routes]
[auto-ap.subs :as subs]
[auto-ap.routes.payments :as payment-routes]
[auto-ap.status :as status]
[auto-ap.views.components.buttons :as buttons]
[auto-ap.views.components.dropdown
:refer
[drop-down drop-down-contents]]
[auto-ap.views.components.grid :as grid]
[auto-ap.views.pages.invoices.form :as form]
[auto-ap.views.pages.invoices.common :refer [invoice-read]]
[auto-ap.views.utils :refer [date->str dispatch-event dispatch-event-with-propagation nf days-until]]
[bidi.bidi :as bidi]
[cemerick.url :as url]
[clojure.string :as str]
[goog.string :as gstring]
[re-frame.core :as re-frame]
[auto-ap.views.components.expense-accounts-dialog :as expense-accounts-dialog]
[auto-ap.views.pages.data-page :as data-page]
[auto-ap.ssr-routes :as ssr-routes]))
(defn data-params->query-params [params]
(if (:exact-match-id params)
{:exact-match-id (some-> params :exact-match-id str)}
{:exact-match-id (some-> params :exact-match-id str)
:start (:start params 0)
:sort (:sort params)
:per-page (:per-page params)
:vendor-id (:id (:vendor params))
:account-id (:id (:account params))
:date-range (:date-range params)
:due-range (:due-range params)
:amount-gte (:amount-gte (:amount-range params))
:amount-lte (:amount-lte (:amount-range params))
:location (:location params)
:unresolved (:unresolved params)
:scheduled-payments (:scheduled-payments params)
:invoice-number-like (:invoice-number-like params)
:import-status (:import-status params)
:status (condp = @(re-frame/subscribe [::subs/active-page])
:invoices nil
:import-invoices nil
:unpaid-invoices :unpaid
:paid-invoices :paid
:voided-invoices :voided)}))
(defn query [params]
{:venia/queries [[:invoice_page
{:filters (data-params->query-params params)}
[[:invoices [:id :total :outstanding-balance :invoice-number :date :due :status :client-identifier :scheduled-payment :source-url :similarity
[:vendor [:name :id]]
[:expense_accounts [:amount :id :location
[:account [:id :name :location]]]]
[:client [:name :id :locations]]
[:payments [:amount :id [:payment [:id :status :amount :s3_url :check_number
[:transaction [:post_date]]]]]]]]
:outstanding
:total_amount
:total
:start
:end]]]})
(re-frame/reg-event-fx
::void-invoice
(fn [{:keys [db]} [_ {id :id}]]
{:graphql
{:token (-> db :user)
:owns-state {:multi ::void
:which id}
:query-obj {:venia/operation {:operation/type :mutation
:operation/name "VoidInvoice"}
:venia/queries [{:query/data [:void-invoice
{:invoice-id id}
invoice-read]}]}
:on-success (fn [result]
[::invoice-updated (assoc (:void-invoice result)
:class "live-removed")])}}))
(re-frame/reg-event-fx
::unvoid-invoice
(fn [{:keys [db]} [_ {id :id}]]
{:graphql
{:token (-> db :user)
:owns-state {:multi ::unvoid
:which id}
:query-obj {:venia/operation {:operation/type :mutation
:operation/name "UnvoidInvoice"}
:venia/queries [{:query/data [:unvoid-invoice
{:invoice-id id}
invoice-read]}]}
:on-success (fn [result]
[::invoice-updated (:unvoid-invoice result)])}}))
(re-frame/reg-event-fx
::unautopay
(fn [{:keys [db]} [_ {id :id}]]
{:graphql
{:token (-> db :user)
:owns-state {:multi ::unautopay
:which id}
:query-obj {:venia/operation {:operation/type :mutation
:operation/name "UnautopayInvoice"}
:venia/queries [{:query/data [:unautopay-invoice
{:invoice-id id}
invoice-read]}]}
:on-success (fn [result]
[::invoice-updated (:unautopay-invoice result)])}}))
(re-frame/reg-event-fx
::invoice-updated
(fn [{:keys [db]} [_ _]]
{:db db}))
(defn row [{:keys [invoice selected-client overrides checkable? actions]}]
(let [{:keys [client status payments expense-accounts invoice-number date due total outstanding-balance id vendor source-url] :as i} invoice
unautopay-states @(re-frame/subscribe [::status/multi ::unautopay])
editing-states @(re-frame/subscribe [::status/multi ::edits])]
[grid/row {:class (:class i) :id id :checkable? checkable? :entity invoice}
(when-not selected-client
[grid/cell {}
(if-let [client-override (:client overrides)]
(client-override i)
(:name client))])
[grid/cell {} (:name vendor)]
[grid/cell {} invoice-number]
[grid/cell {:class "is-hidden-mobile"} (date->str date) ]
[grid/cell {}
(when due
(if (#{":paid" :paid ":voided" :voided} status)
nil
(let [due-in (days-until due)]
(if (> due-in 0)
[:span.has-text-success due-in " days"]
[:span.has-text-danger due-in " days"])
)))]
[grid/cell {} (str/join ", " (set (map :location expense-accounts)))]
[grid/cell {:class "has-text-right"} (nf total )]
[grid/cell {:class "has-text-right"}
(when (:scheduled-payment i)
[:<> [:div.tag.is-info.is-light "Autopay"] " "])
(nf outstanding-balance )]
[grid/button-cell {}
[:div.buttons
(when (seq expense-accounts)
[drop-down {:id [::expense-accounts id ]
:is-right? true
:header [buttons/sl-icon {:class "badge"
:event [::events/toggle-menu [::expense-accounts id]]
:data-badge (str (clojure.core/count expense-accounts))
:icon "icon-navigation-menu"}]}
[drop-down-contents
[:div
(for [e expense-accounts]
^{:key (:id e)}
[:span.dropdown-item (:name (:account e)) " " (gstring/format "$%.2f" (:amount e) ) ])
(when (get actions :expense-accounts)
[:<>
[:hr.dropdown-divider]
[:a.dropdown-item.is-primary {:on-click (dispatch-event [::expense-accounts-dialog/show i])} "Change"]])]]])
[:span {:style {:margin-left "1em"}}]
(when (or (seq payments)
source-url
)
[:<>
[drop-down {:id [::payments id]
:is-right? true
:header [buttons/fa-icon {:class "badge"
:on-click (dispatch-event-with-propagation [::events/toggle-menu [::payments id]])
:data-badge (str (cond-> (clojure.core/count payments)
source-url inc))
:icon "fa-paperclip"}]}
[drop-down-contents
[:div.dropdown-item
[:table.table.grid.compact
[:tbody
(for [invoice-payment payments]
^{:key (:id invoice-payment)}
[:tr
[:td
"Payment"]
[:td (gstring/format "$%.2f" (:amount invoice-payment) )]
[:td
(when (= :cleared (:status (:payment invoice-payment)))
(str "cleared")
)]
[:td (:post-date (:transaction (:payment invoice-payment)))]
[:td
[buttons/fa-icon {:icon "fa-external-link"
:href (str (bidi/path-for ssr-routes/only-routes ::payment-routes/page )
"?"
(url/map->query {:exact-match-id (:id (:payment invoice-payment))}))}]]])
(when source-url
[:tr
[:td
"File"]
[:td {:colspan 4}
[buttons/fa-icon {:icon "fa-external-link"
:target "_new"
:href source-url}]]])]]]]]
[:span {:style {:margin-right "1em"}}]])
(when (and (get actions :edit)
(not= ":voided" (:status i)))
[buttons/fa-icon {:icon "fa-pencil"
:class (status/class-for (get editing-states id))
:event
[::events/vendor-preferences-requested {:client-id (:id client)
:vendor-id (:id vendor)
:on-success [::form/editing i]
:on-failure []
:owns-state {:multi ::edits
:which (:id i)}}]}])
(when (and (get actions :void)
(= (:outstanding-balance i) (:total i)) (not= ":voided" (:status i)))
[buttons/sl-icon {:icon "icon-bin-2"
:event [::void-invoice i]}])
(when (and (get actions :void)
(= ":voided" (:status i)))
[buttons/fa-icon {:icon "fa-undo"
:event [::unvoid-invoice i]}])
(when (and (get actions :void)
(= ":paid" (:status i))
(:scheduled-payment i)
(not (seq (:payments i))))
[buttons/fa-icon {:icon "fa-undo"
:class (status/class-for (get unautopay-states (:id i)))
:event [::unautopay i]}])]]]))
(defn invoice-table [{:keys [check-boxes overrides actions data-page checkable-fn action-buttons]}]
(let [{:keys [data status params table-params]} @(re-frame/subscribe [::data-page/page data-page])
selected-client @(re-frame/subscribe [::subs/client])
x @(re-frame/subscribe [::subs/selected-clients])
is-loading? (= :loading (:state status))
is-sorted-by-vendor? (and (= "vendor" (:sort-key (first (:sort table-params))))
(not is-loading?)
(or (apply <= (map (comp :name :vendor) (:data data)))
(apply >= (map (comp :name :vendor) (:data data)))))
[invoice-groups] (if is-sorted-by-vendor?
(reduce
(fn [[acc last-vendor] invoice]
(if (not= (:id (:vendor invoice))
last-vendor)
[(update-in acc [(clojure.core/count acc)] #(conj (or % []) invoice))
(:id (:vendor invoice))]
[(update-in acc [(dec (clojure.core/count acc))] #(conj (or % []) invoice))
(:id (:vendor invoice))]))
[[] nil]
(:data data))
[[(:data data)]])]
[grid/grid {:data-page data-page
:check-boxes? check-boxes
:column-count (if selected-client 8 9)}
[grid/controls (assoc data :action-buttons action-buttons)
[:div.level-item
[:div.tags
[:div.tag.is-info.is-light "Outstanding " (nf (:outstanding data))]
[:div.tag.is-info.is-light " Total " (nf (:total-amount data))]]]]
(for [invoices invoice-groups]
^{:key (or (:id (first invoices)) "init")}
[grid/table {:fullwidth true}
[grid/header {}
[grid/row {:id "header"
:entity params}
(when-not selected-client
[grid/sortable-header-cell {:sort-key "client" :sort-name "Client"} "Client"])
[grid/sortable-header-cell {:sort-key "vendor" :sort-name "Vendor"}
(if is-sorted-by-vendor?
(:name (:vendor (first invoices)))
"Vendor")]
[grid/sortable-header-cell {:sort-key "invoice-number" :sort-name "Invoice Number"} "Invoice #"]
[grid/sortable-header-cell {:sort-key "date" :sort-name "Date" :style {:width "8em"}} "Date"]
[grid/sortable-header-cell {:sort-key "due" :sort-name "Due" :style {:width "8em"} :class "is-hidden-mobile"} "Due"]
[grid/sortable-header-cell {:sort-key "location" :sort-name "Location" :style {:width "5em"}} "Loc"]
[grid/sortable-header-cell {:sort-key "total" :sort-name "Total" :style {:width "8em"} :class "has-text-right"} "Total"]
[grid/sortable-header-cell {:sort-key "outstanding-balance" :sort-name "Outstanding" :style {:width "10em"} :class "has-text-right"} "Outstanding"]
[grid/header-cell {:style {:width "14rem"}}]]]
[grid/body
(for [{:keys [id] :as i} invoices]
^{:key id}
[row {:invoice i
:selected-client selected-client
:checkable? (if checkable-fn
(checkable-fn i)
true)
:actions actions
:overrides overrides}])]])
[grid/bottom-paginator data]]))

View File

@@ -4,8 +4,6 @@
[auto-ap.subs :as subs]
[auto-ap.permissions :as p]
[auto-ap.views.components.layouts :refer [loading-layout]]
[auto-ap.views.pages.unpaid-invoices :refer [unpaid-invoices-page paid-invoices-page all-invoices-page voided-invoices-page]]
[auto-ap.views.pages.import-invoices :refer [import-invoices-page]]
[auto-ap.views.pages.needs-activation :refer [needs-activation-page]]
[auto-ap.views.pages.transactions :refer [transactions-page]]
[auto-ap.views.pages.ledger :refer [ledger-page]]
@@ -17,38 +15,10 @@
[auto-ap.views.pages.ledger.cash-flows :refer [cash-flows-page]]
[auto-ap.views.pages.ledger.profit-and-loss-detail :refer [profit-and-loss-detail-page]]
[auto-ap.views.pages.login :refer [login-page]]
[auto-ap.views.pages.payments :refer [payments-page]]
[auto-ap.views.pages.home :refer [home-page home-page-with-vendor]]))
(defmulti page (fn [active-page] active-page))
(defmethod page :unpaid-invoices [_]
(when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :invoice-page})
^{:key :unpaid}
[unpaid-invoices-page {:status :unpaid}]))
(defmethod page :import-invoices [_]
(when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :invoice-page})
(import-invoices-page )))
(defmethod page :paid-invoices [_]
(when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :invoice-page})
^{:key :paid}
[paid-invoices-page {:status :paid}]))
(defmethod page :voided-invoices [_]
(when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :invoice-page})
^{:key :voided}
[voided-invoices-page {:status :voided}]))
(defmethod page :invoices [_]
(when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :invoice-page})
^{:key :all}
[all-invoices-page {}]))
(defmethod page :payments [_]
(when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :payment-page})
[payments-page]))
(defmethod page :transactions [_]
(when (p/can? @(re-frame/subscribe [::subs/user]) {:subject :transaction-page})

View File

@@ -1,221 +0,0 @@
(ns auto-ap.views.pages.import-invoices
(:require [re-frame.core :as re-frame]
[reagent.core :as reagent]
[reagent.dom :as rdom]
[auto-ap.subs :as subs]
[auto-ap.views.components.layouts :refer [side-bar-layout]]
[auto-ap.views.components.invoices.side-bar :refer [invoices-side-bar]]
[auto-ap.views.utils :refer [dispatch-event with-user ->%]]
[auto-ap.utils :refer [by]]
[auto-ap.views.components.invoice-table :refer [invoice-table] :as invoice-table]
[cljs.reader :as edn]
[auto-ap.status :as status]
[vimsical.re-frame.fx.track :as track]
#_{:clj-kondo/ignore [:unused-namespace]}
[dropzone :as dz]
[auto-ap.views.pages.data-page :as data-page]
[clojure.set :as set]
[auto-ap.effects.forward :as forward]
[goog.string :as gstring]
[auto-ap.views.components.typeahead.vendor :refer [search-backed-typeahead]]))
(defn dropzone []
(let [client (re-frame/subscribe [::subs/client])
token (re-frame/subscribe [::subs/token])
vendor (reagent/atom nil)]
(reagent/create-class
{:display-name "dropzone"
:component-did-mount (fn [this]
(dz. (rdom/dom-node this)
(clj->js {:init (fn []
(let [^js/Dropzone t (js-this)]
(.on t "addedfiles"
(fn []
(re-frame/dispatch [::status/completed ::import])))
(.on t "success" (fn [_ _]
(re-frame/dispatch [::invalidated])))
(.on t "error" (fn [_ error]
(re-frame/dispatch [::status/error ::import [(edn/read-string error)]])))))
:paramName "file"
:headers {"Authorization" (str "Token " @token)}
:url (str "/api/invoices/upload"
(when-let [client-name (-> @client :id)]
(str "?client=" client-name)))
:previewsContainer "#dz-hidden"
:previewTemplate "<div class='dz-hidden-preview'></div>"})))
:reagent-render (fn []
[:form.dz {:action "/api/invoices/upload"}
[:div.field.has-addons
[:p.control
[:a.button.is-static "Force Location"]]
[:p.control
[:input.input {:name "location" :placeholder "SG" :size "4" :maxlength "2" :style {:display "inline"}}]]]
[:div.field.has-addons
[:p.control
[:a.button.is-static "Force vendor"]]
[:div.control {:style {:width "400px"}}
[search-backed-typeahead {:search-query (fn [i]
[:search_vendor
{:query i}
[:name :id]])
:type "typeahead-v3"
:name "vendor"
:on-change (fn [v]
(reset! vendor v))
:value @vendor}]]]
[:div.tile.notification
[:div.has-text-centered {:style {:padding "80px 0px" :width "100%"}}
[:span
[:span {:class "icon"}
[:i {:class "fa fa-cloud-download"}]]
"Drop any invoices you want to process here"]]]])})))
(re-frame/reg-sub
::batch
(fn [db]
(-> db (::batch 0))))
(re-frame/reg-event-fx
::invalidated
(fn [{:keys [db]} _]
{:dispatch-n [[::params-change @(re-frame/subscribe [::data-page/params :import-invoices])]
[::data-page/reset-checked :import-invoices]]
:db (update db ::batch inc)}))
(re-frame/reg-event-fx
::mounted
(fn [_ _]
{::track/register [{:id ::params
:subscription [::data-page/params :import-invoices]
:event-fn (fn [params]
[::params-change params])}
]
::forward/register [{:id ::received
:events #{::data-page/received}
:event-fn (fn [[_ _ {:keys [data]}]]
[::received data])}]}))
(re-frame/reg-event-fx
::unmounted
(fn [_ _]
{::track/dispose {:id ::params}
:dispatch [::data-page/dispose :import-invoices]}))
(re-frame/reg-event-fx
::params-change
[with-user]
(fn [{:keys [user] } [_ params]]
{:graphql {:token user
:owns-state {:single [::data-page/page :import-invoices]}
:query-obj (invoice-table/query (assoc params :import-status "pending"))
:on-success (fn [result]
(let [result (set/rename-keys (first (:invoice-page result))
{:invoices :data})]
[::data-page/received :import-invoices result]))}}))
(re-frame/reg-event-fx
::received
(fn [_ [_ data]]
{:dispatch [::data-page/toggle-check :import-invoices (by :id data)]}))
(re-frame/reg-event-fx
::reject-invoices-clicked
(fn [{:keys [db]} [_ invoices]]
{:graphql
{:token (-> db :user)
:owns-state {:single ::reject}
:query-obj {:venia/operation {:operation/type :mutation
:operation/name "RejectInvoices"}
:venia/queries [[:reject-invoices
{:invoices (vec invoices)}
[]]]}
:on-success [::invalidated]}}))
(re-frame/reg-event-fx
::approve-invoices-clicked
(fn [{:keys [db]} [_ invoices]]
{:graphql
{:token (-> db :user)
:owns-state {:single ::approve}
:query-obj {:venia/operation {:operation/type :mutation
:operation/name "ApproveInvoices"}
:venia/queries [[:approve-invoices
{:invoices (vec invoices)}
[]]]}
:on-success [::invalidated]}}))
(defn approve-reject-button [checked]
[:div.buttons
[:button.button.is-primary {:on-click (dispatch-event [::approve-invoices-clicked checked])
:class (status/class-for @(re-frame/subscribe [::status/single ::approve]))
:disabled (or (not (boolean (seq checked)))
(status/disabled-for @(re-frame/subscribe [::status/single ::approve])))}
"Approve "
(str
(count checked)
" invoices")
[:span " "]]
[:button.button.is-warning {:on-click (dispatch-event [::reject-invoices-clicked checked])
:class (status/class-for @(re-frame/subscribe [::status/single ::reject]))
:disabled (or (not (boolean (seq checked)))
(status/disabled-for @(re-frame/subscribe [::status/single ::reject])))}
"Reject "
(str
(count checked)
" invoices")
[:span " "]]])
(def import-invoices-content
(with-meta
(fn []
(let [page @(re-frame/subscribe [::data-page/page :import-invoices])
batch @(re-frame/subscribe [::batch])
client (:id @(re-frame/subscribe [::subs/client]))
checked-set (into #{} (:checked-set page))]
^{:key (str client "-" batch)}
[:div
[:h1.title "Upload invoices"]
[status/status-notification {:statuses [[::status/single ::approve]
[::status/single ::reject]
[::status/single ::import]]}]
^{:key (str batch)}
[dropzone]
[:div.mb-4]
[:div {:class "card found-invoices",}
[:div {:class "card-header"}
[:span {:class "card-header-title"} "Found Invoices"]]
[:div {:class "card-content"}
[approve-reject-button (if (get checked-set "header")
(into #{} (map :id (:data (:data page))))
checked-set)]
(if (seq (:data (:data page)))
[invoice-table {:id :approved
:data-page :import-invoices
:overrides {:client (fn [row]
[:p (:name (:client row))
[:p [:i.is-size-7 (:client-identifier row)]
" "
[:span
{:style {:background-color (gstring/format "rgba(255, 0,0,%.2f)" (- 1.0 (:similarity row)))
}}
(->% (:similarity row))]]])}
:check-boxes true}]
[:span "No pending invoices"])]]]))
{:component-did-mount (fn []
(re-frame/dispatch-sync [::mounted]))
:component-will-unmount (fn []
(re-frame/dispatch-sync [::unmounted]))}))
(defn import-invoices-page []
[side-bar-layout {:side-bar [invoices-side-bar {}]
:main [import-invoices-content ]}])

View File

@@ -1,131 +0,0 @@
(ns auto-ap.views.pages.invoices.advanced-print-checks
(:require
[auto-ap.forms :as forms]
[auto-ap.forms.builder :as form-builder]
[auto-ap.schema :as schema]
[auto-ap.status :as status]
[auto-ap.subs :as subs]
[auto-ap.views.components :as com]
[auto-ap.views.components.modal :as modal]
[auto-ap.views.components.money-field :refer [money-field]]
[auto-ap.views.pages.invoices.common
:refer [does-amount-exceed-outstanding? invoice-read]]
[auto-ap.views.utils :refer [coerce-float dispatch-event with-user]]
[malli.core :as m]
[re-frame.core :as re-frame]))
(def advanced-print-schema (m/schema
[:and
[:map
[:bank-account-id schema/not-empty-string]
[:invoice-amounts [:map-of
:string
[:map
[:amount schema/money]]]]]
[:fn (fn [{:keys [invoices invoice-amounts]}]
(if (seq (filter
(fn [{:keys [id outstanding-balance]}]
(does-amount-exceed-outstanding? (get-in invoice-amounts [id :amount]) outstanding-balance ))
invoices))
(throw (ex-info "Invalid" {:type ::too-much-invoice}))
true))]]))
(defn form []
(let [real-bank-accounts @(re-frame/subscribe [::subs/real-bank-accounts])
{:keys [data]} @(re-frame/subscribe [::forms/form ::form])]
[form-builder/builder {:submit-event [::try-save]
:id ::form
:schema advanced-print-schema}
[form-builder/field-v2 {:field :bank-account-id}
"Pay using"
[com/select-field {:options (for [{:keys [id name]} real-bank-accounts]
[id name])
:allow-nil? true}]]
[:table.table.is-fullwidth
[:thead
[:tr
[:th "Vendor"]
[:th "Invoice ID"]
[:th {:style {"width" "10em"}} "Payment"]]]
[:tbody
(doall
(for [{:keys [vendor invoice-number id]} (:invoices data)]
^{:key id}
[:tr
[:td (:name vendor)]
[:td invoice-number]
[:td
[form-builder/raw-field-v2 {:field [:invoice-amounts id :amount]}
[money-field]]]]))]]]))
(re-frame/reg-event-fx
::show
(fn [{:keys [db]} [_ invoices]]
{:dispatch [::modal/modal-requested {:title "Print Checks"
:body [form]
:confirm {:value "Print checks"
:status-from [::status/single ::form]
:class "is-primary"
:on-click (dispatch-event [::try-save])
:close-event [::status/completed ::form]}}]
:db (-> db
(forms/start-form ::form
{:invoices invoices
:invoice-amounts (into {}
(map (fn [i] [(:id i)
{:amount (coerce-float (:outstanding-balance i))}])
invoices))}))}))
(re-frame/reg-event-fx
::try-save
[(forms/in-form ::form)]
(fn [{:keys [db]}]
#_(println (m/explain advanced-print-schema (:data db)))
(if (not (m/validate advanced-print-schema (:data db)))
{:dispatch-n [[::status/error ::form [{:message "Please correct any errors and try again"}]]
[::forms/attempted-submit ::form]]}
{:dispatch [::save]})))
(re-frame/reg-event-fx
::save
[with-user (forms/in-form ::form) ]
(fn [{:keys [db user]} _]
(let [type (or (->> @(re-frame/subscribe [::subs/client])
:bank-accounts
(filter #(= (:bank-account-id (:data db)) (:id %)))
first
:type)
:check)
{:keys [invoices invoice-amounts bank-account-id]} (:data db)]
{:graphql
{:token user
:owns-state {:single ::form}
:query-obj {:venia/operation {:operation/type :mutation
:operation/name "PrintChecks"}
:venia/queries [[:print-checks
{:invoice_payments (map (fn [x]
{:invoice-id (:id x)
:amount (get-in invoice-amounts [(:id x) :amount])})
invoices)
:type type
:bank_account_id bank-account-id
:client_id (->> invoices first :client :id)}
[[:invoices invoice-read]
:pdf_url]]]}
:on-success (fn [result]
[::checks-printed
(:invoices (:print-checks result))
(:pdf-url (:print-checks result))])}})))
(re-frame/reg-event-fx
::checks-printed
(fn [{:keys [_]} [_ _]]
{:dispatch [::modal/modal-closed]}))

View File

@@ -1,97 +0,0 @@
(ns auto-ap.views.pages.invoices.bulk-change
(:require
[auto-ap.forms :as forms]
[auto-ap.status :as status]
[auto-ap.subs :as subs]
[auto-ap.views.components.expense-accounts-field
:as expense-accounts-field
:refer [expense-accounts-field-v2]]
[auto-ap.views.components.modal :as modal]
[auto-ap.views.pages.data-page :as data-page]
[auto-ap.views.components.invoice-table
:refer [data-params->query-params]]
[auto-ap.views.utils :refer [dispatch-event with-user]]
[clojure.string :as str]
[re-frame.core :as re-frame]
[reagent.core :as r]
[vimsical.re-frame.fx.track :as track]
[auto-ap.events :as events]
[vimsical.re-frame.cofx.inject :as inject]
[auto-ap.forms.builder :as form-builder]
[auto-ap.views.components :as com]))
(re-frame/reg-event-fx
::coded
(fn [_ [_ _ _]]
{:dispatch-n [[::modal/modal-closed]
[:auto-ap.views.pages.unpaid-invoices/params-change
@(re-frame/subscribe [::data-page/params :auto-ap.views.pages.unpaid-invoices/invoices])]]}))
(re-frame/reg-event-fx
::code-selected
[with-user (forms/in-form ::form) (re-frame/inject-cofx ::inject/sub [::subs/client])]
(fn [{:keys [user db] ::subs/keys [client]} [_ checked]]
(let [checked-params (get checked "header")
specific-invoices (map :id (vals (dissoc checked "header")))
data (:data db)]
{:graphql
{:token user
:owns-state {:single ::form}
:query-obj {:venia/operation {:operation/type :mutation
:operation/name "BulkChangeInvoices"}
:venia/queries [[:bulk-change-invoices
{:filters (some-> checked-params data-params->query-params)
:client_id (:id client)
:ids specific-invoices
:accounts (map
#(-> %
(update :id (fn [i] (if (some-> i (str/starts-with? "new-"))
nil
i)))
(assoc :percentage (/ (get-in % [:amount-percentage]) 100 ))
(assoc :account-id (get-in % [:account :id]))
(select-keys [:percentage :id :location :account-id]))
(:accounts data))}
[:message]
]]}
:on-success (fn [result]
[::coded
(:message result)
checked-params
])}})))
(defn form-content [_]
(let [{:keys [data]} @(re-frame/subscribe [::forms/form ::form])]
[form-builder/builder {:submit-event [::code-selected]
:id ::form}
[form-builder/raw-field-v2 {:field :accounts}
[expense-accounts-field-v2 {:descriptor "account asssignment"
:percentage-only? true
:client (:client data)
:locations (into ["Shared"] @(re-frame/subscribe [::subs/locations-for-client (:id (:client data))]))
:max 100}]]]))
(defn form [_]
(r/create-class
{:display-name "invoice-bulk-change-form"
:reagent-render (fn [p]
[form-content p])}))
(re-frame/reg-event-fx
::bulk-change-requested
(fn [{:keys [db]} [_ checked params total-visible]]
(let [to-change (if (get checked "header")
[:b.strong.has-text-danger "all " total-visible " visible invoices"]
(str (count checked) " invoices"))]
{:dispatch [::modal/modal-requested {:title "Confirmation"
:body [:div "Please fill in the details on how to code " to-change ":"
[form ]]
:cancel? true
:confirm {:value "Code"
:class "is-danger"
:status-from [::status/single ::form]
:on-click (dispatch-event [::code-selected checked] )}
:close-event [::status/completed ::code-selected]}]
:db (-> db
(forms/start-form ::form {:accounts []
:client @(re-frame/subscribe [::subs/client (:client-id params)])}))})))

View File

@@ -1,22 +0,0 @@
(ns auto-ap.views.pages.invoices.common)
(defn does-amount-exceed-outstanding? [amount outstanding-balance ]
(let [amount (js/parseFloat amount)
outstanding-balance (js/parseFloat outstanding-balance)]
(or (and (> outstanding-balance 0)
(> amount outstanding-balance))
(and (> outstanding-balance 0)
(<= amount 0))
(and (< outstanding-balance 0)
(< amount outstanding-balance))
(and (< outstanding-balance 0)
(>= amount 0)))))
(def invoice-read [:id :total :outstanding-balance :date :due :invoice-number :status
:scheduled-payment
[:client [:id :name :locations]]
[:payments [:amount [:payment [:id :amount :s3_url :check_number ]]]]
[:vendor [:id :name]]
[:expense_accounts [:amount :id
:location
[:account [:id :numeric-code :name :location]]]]])

View File

@@ -1,459 +0,0 @@
(ns auto-ap.views.pages.invoices.form
(:require
[auto-ap.events :as events]
[auto-ap.forms :as forms]
[auto-ap.forms.builder :as form-builder]
[auto-ap.schema :as schema]
[auto-ap.status :as status]
[auto-ap.subs :as subs]
[auto-ap.time-utils :refer [next-dom]]
[auto-ap.utils :refer [dollars=]]
[auto-ap.views.components :as com]
[auto-ap.views.components.dropdown :refer [drop-down]]
[auto-ap.views.components.expense-accounts-field
:as eaf
:refer [expense-accounts-field-v2 recalculate-amounts]]
[auto-ap.views.components.layouts :as layouts]
[auto-ap.views.components.level :refer [left-stack]]
[auto-ap.views.components.modal :as modal]
[auto-ap.views.pages.invoices.common :refer [invoice-read]]
[auto-ap.views.utils :refer [date-picker dispatch-event with-user date->str]]
[cljs-time.core :as c]
[clojure.string :as str]
[malli.core :as m]
[re-frame.core :as re-frame]
[reagent.core :as r]
[vimsical.re-frame.cofx.inject :as inject]
[vimsical.re-frame.fx.track :as track]))
(def schema (m/schema
[:map
[:client schema/reference]
[:vendor schema/reference]
[:date schema/date]
[:due {:optional true} [:maybe schema/date]]
[:scheduled-payment {:optional true} [:maybe schema/date]]
[:invoice-number schema/not-empty-string]
[:total schema/money]
[:expense-accounts eaf/schema]]))
;; SUBS
(re-frame/reg-sub
::can-submit
:<- [::forms/form ::form]
(fn [{:keys [data]} _]
(let [min-total (if (= (:total (:original data)) (:outstanding-balance (:original data)))
nil
(- (:total (:original data)) (:outstanding-balance (:original data))))
account-total (reduce + 0 (map :amount (:expense-accounts data)))]
(and
(or (not min-total) (>= (:total data) min-total))
(or (not (:id data))
(dollars= (Math/abs (:total data)) (Math/abs account-total)))))))
(re-frame/reg-sub
::create-query
:<- [::forms/form ::form]
(fn [{{:keys [invoice-number date due scheduled-payment location total expense-accounts vendor client]} :data}]
{:venia/operation {:operation/type :mutation
:operation/name "AddInvoice"}
:venia/queries [{:query/data [:add-invoice
{:invoice {
:vendor-id (:id vendor)
:client-id (:id client)
:date date
:scheduled-payment scheduled-payment
:due due
:invoice-number (some-> invoice-number str/trim)
:location location
:total total
:expense-accounts (map (fn [ea]
{:id (when-not (str/starts-with? (:id ea) "new-")
(:id ea))
:account_id (:id (:account ea))
:location (:location ea)
:amount (:amount ea)})
expense-accounts)}}
invoice-read]}]}))
(re-frame/reg-sub
::edit-query
:<- [::forms/form ::form]
(fn [{{:keys [id invoice-number date due scheduled-payment total expense-accounts]} :data}]
{:venia/operation {:operation/type :mutation
:operation/name "EditInvoice"}
:venia/queries [{:query/data [:edit-invoice
{:invoice {:id id
:invoice-number invoice-number
:date date
:scheduled-payment scheduled-payment
:due due
:total total
:expense-accounts (map (fn [ea]
{:id (when-not (str/starts-with? (:id ea) "new-")
(:id ea))
:account_id (:id (:account ea))
:location (:location ea)
:amount (:amount ea)})
expense-accounts)}}
invoice-read]}]}))
;; EVENTS
(re-frame/reg-event-fx
::updated
[
(re-frame/inject-cofx ::inject/sub (fn [[_ _ _ client]]
[::subs/locations-for-client (:id client)]))]
(fn [{:keys [db] ::subs/keys [locations-for-client]} [_ _ command client]]
(when (= :create command)
{:db
(-> db
(forms/stop-form ::form )
(forms/start-form ::form {:client client
:status :unpaid
:date (c/now)
:expense-accounts
(eaf/from-graphql []
0.0
locations-for-client)}))})))
(re-frame/reg-event-fx
::adding
[(re-frame/inject-cofx ::inject/sub (fn [[_ which _]]
[::subs/locations-for-client (:id (:client which))]))]
(fn [{:keys [db] ::subs/keys [locations-for-client]} [_ new]]
{:db
(-> db (forms/start-form ::form (assoc new :expense-accounts
(eaf/from-graphql (:expense-accounts new)
0.0
locations-for-client))))}))
(re-frame/reg-event-fx
::editing
[(re-frame/inject-cofx ::inject/sub (fn [[_ which _]]
[::subs/locations-for-client (:id (:client which))]))]
(fn [{:keys [db] ::subs/keys [locations-for-client]} [_ which vendor-preferences]]
(let [edit-invoice which]
{:db
(-> db
(forms/start-form ::form {:id (:id edit-invoice)
:payments (:payments edit-invoice)
:status (:status edit-invoice)
:date (:date edit-invoice)
:due (:due edit-invoice)
:vendor-preferences vendor-preferences
:scheduled-payment (:scheduled-payment edit-invoice)
:invoice-number (:invoice-number edit-invoice)
:total (cond-> (:total edit-invoice)
(not (str/blank? (:total edit-invoice))) (js/parseFloat ))
:original which
:vendor (:vendor edit-invoice)
:client (:client edit-invoice)
:expense-accounts (eaf/from-graphql (:expense-accounts which)
(:total which)
locations-for-client)}))})))
(re-frame/reg-event-db
::changed
(forms/change-handler ::form
(fn [data field value]
(cond
(= [:vendor-preferences] field)
(cond-> []
(eaf/can-replace-with-default? (:expense-accounts data))
(into [[:expense-accounts] (eaf/default-account (:expense-accounts data)
(:default-account value)
(:total data)
(:locations (:client data)))])
(:automatically-paid-when-due value)
(into [[:scheduled-payment] (:due data)
[:schedule-when-due] true])
(:schedule-payment-dom value)
(into [[:scheduled-payment] (next-dom (:date data) (:schedule-payment-dom value))]))
(= [:total] field)
[[:expense-accounts] (recalculate-amounts (:expense-accounts data) value)]
(and (= [:date] field)
(:schedule-payment-dom (:vendor-preferences data)))
[[:scheduled-payment] (next-dom value (:schedule-payment-dom (:vendor-preferences data))) ]
(and (= [:schedule-when-due] field) value)
[[:scheduled-payment] (:due data)]
(and (= [:due] field) (:schedule-when-due data))
[[:scheduled-payment] value]
:else
[]))))
(re-frame/reg-event-db
::maybe-change-client
[ (forms/in-form ::form) ]
(fn [{:keys [data] :as f} [_ c]]
(if (and (not (:id data))
(:id c))
(assoc-in f [:data :client] c)
f)))
(re-frame/reg-event-fx
::add-and-print
[with-user (forms/in-form ::form)]
(fn [{:keys [user]
{{:keys [invoice-number date location total expense-accounts scheduled-payment vendor client]
:as data} :data} :db} [_ bank-account-id type]]
(if (not (m/validate schema data))
{:dispatch-n [[::status/error ::form [{:message "Please fix the errors and try again."}]]
[::forms/attempted-submit ::form]]}
{:graphql
{:token user
:owns-state {:single ::form}
:query-obj {:venia/operation {:operation/type :mutation
:operation/name "AddAndPrintInvoice"}
:venia/queries [{:query/data [:add-and-print-invoice
{:invoice {:date date
:vendor-id (:id vendor)
:client-id (:id client)
:scheduled-payment scheduled-payment
:invoice-number invoice-number
:location location
:total total
:expense-accounts (map (fn [ea]
{:id (when-not (str/starts-with? (:id ea) "new-")
(:id ea))
:account_id (:id (:account ea))
:location (:location ea)
:amount (:amount ea)})
expense-accounts)}
:bank-account-id bank-account-id
:type type}
[:pdf-url [:invoices invoice-read]]]}]}
:on-success [::added-and-printed client]
:on-error [::forms/save-error ::form]}})
))
(re-frame/reg-event-fx
::saving
[with-user (forms/in-form ::form) (re-frame/inject-cofx ::inject/sub [::edit-query]) (re-frame/inject-cofx ::inject/sub [::create-query])]
(fn [{:keys [user] {:keys [data]} :db ::keys [edit-query create-query]} _]
(if (not (m/validate schema data))
{:dispatch-n [[::status/error ::form [{:message "Please fix the errors and try again."}]]
[::forms/attempted-submit ::form]]}
{:graphql
{:token user
:owns-state {:single ::form}
:query-obj (if (:id data)
edit-query
create-query)
:on-success (fn [result]
[::updated
(assoc (if (:id data)
(:edit-invoice result)
(:add-invoice result))
:class "live-added")
(if (:id data)
:edit
:create)
(:client data)])
:on-error [::forms/save-error ::form]}})))
(re-frame/reg-event-fx
::save-requested
[with-user (forms/in-form ::form)]
(fn [{:keys [db]} [_ fwd-event]]
(if (and (:scheduled-payment (:data db))
(not (:vendor-autopay? (:vendor-preferences (:data db)))))
{:dispatch
[::modal/modal-requested {:title "Scheduled payment date"
:body [:div "This vendor isn't set up to be automatically paid. On "
(date->str (:scheduled-payment (:data db)))
" the invoice will be marked as paid, but no payment will be made to the vendor. "
"Are you sure you want to continue?"]
:confirm {:value "Save"
:class "is-warning"
:on-click #(do (re-frame/dispatch [::modal/modal-closed])
(re-frame/dispatch fwd-event))}
:cancel? true}]}
{:dispatch fwd-event})))
(re-frame/reg-event-fx
::added-and-printed
(fn [_ [_ client result]]
(let [invoice (first (:invoices (:add-and-print-invoice result)))]
{:dispatch-n [[::updated (assoc invoice :class "live-added") :create client]
[::checks-printed [invoice] (:pdf-url (:add-and-print-invoice result))]]})))
(re-frame/reg-event-db
::checks-printed
(fn [db [_]]
db))
(re-frame/reg-event-fx
::changed-vendor
[(forms/in-form ::form)]
(fn [{{{:keys [client]} :data} :db} [_ vendor]]
(when (and (:id client) (:id vendor))
{:dispatch [::events/vendor-preferences-requested {:client-id (:id client)
:vendor-id (:id vendor)
:on-success [::changed [:vendor-preferences]]
:on-failure [:hello]}]})))
;; VIEWS
(re-frame/reg-event-fx
::mounted
(fn []
{::track/register [{:id ::client
:subscription [::subs/client]
:event-fn (fn [c]
[::maybe-change-client c])}
{:id ::vendor-change
:subscription [::forms/field ::form [:vendor]]
:event-fn (fn [v]
[::changed-vendor v])}]}))
(re-frame/reg-event-fx
::unmounted
(fn []
{::track/dispose [{:id ::client}
{:id ::vendor-change}]}))
(defn form-content []
[layouts/side-bar {:on-close (dispatch-event [::forms/form-closing ::form ])}
(let [{:keys [data]} @(re-frame/subscribe [::forms/form ::form])
can-submit? (boolean @(re-frame/subscribe [::can-submit]))
status @(re-frame/subscribe [::status/single ::form])
active-client @(re-frame/subscribe [::subs/client])
exists? (:id data)
can-change-amount? (#{:unpaid ":unpaid"} (:status data))
min-total (if (= (:total (:original data)) (:outstanding-balance (:original data)))
nil
(- (:total (:original data)) (:outstanding-balance (:original data))))]
[form-builder/builder {:can-submit [::can-submit]
:change-event [::changed]
:submit-event [::save-requested [::saving ]]
:id ::form
:schema schema}
[form-builder/section {:title [:div "New Invoice "
(cond
(#{:unpaid ":unpaid"} (:status data))
nil
(#{:voided ":voided"} (:status data))
[:div.tag.is-info.is-light "Voided"]
(and (#{:paid ":paid"} (:status data))
(not (seq (:payments data))))
[:div.tag.is-info.is-light "Automatically paid"]
(#{:paid ":paid"} (:status data))
(if-let [check-number (:check-number (:payment (first (:payments data))))]
[:div.tag.is-info.is-light "Paid by check #" check-number ]
[:div.tag.is-info.is-light "Paid"])
:else
nil)]}
(when-not active-client
[form-builder/field-v2 {:required? true
:field [:client]}
"Client"
[com/entity-typeahead {:entities @(re-frame/subscribe [::subs/clients])
:entity->text :name
:style {:width "18em"}
:auto-focus (if active-client false true)
:disabled exists?}]])
[form-builder/field-v2 {:required? true
:field [:vendor]}
"Vendor"
[com/search-backed-typeahead {:disabled exists?
:search-query (fn [i]
[:search_vendor
{:query i}
[:name :id]])
:style {:width "18em"}
:auto-focus (if active-client true false)}]]
[form-builder/field-v2 {:required? true
:field :date}
"Date"
[date-picker {:output :cljs-date}]]
[form-builder/field-v2 {:field [:due]}
"Due (optional)"
[date-picker {:output :cljs-date}]]
[form-builder/vertical-control
"Scheduled payment (optional)"
[left-stack
[:div.control
[form-builder/raw-field-v2 {:field :scheduled-payment}
[date-picker {:output :cljs-date}]]
[form-builder/raw-error-v2 {:field :scheduled-payment}]]
[:div.control
[form-builder/raw-field-v2 {:field :schedule-when-due}
[com/switch-input {:id "schedule-when-due"
:label "Same as due date"}]]]]]
[form-builder/field-v2 {:required? true
:field :invoice-number}
"Invoice #"
[:input.input {:style {:width "12em"}}]]
[form-builder/field-v2 {:required? true
:field :total}
"Total"
[com/money-input {:disabled (if can-change-amount? "" "disabled")
:style {:max-width "8em"}
:min min-total}]]]
[form-builder/field-v2 {:field :expense-accounts}
"Expense Accounts"
[expense-accounts-field-v2 {:descriptor "expense account"
:vendor-id (:id (:vendor data))
:allowance :invoice
:locations (:locations (:client data))
:max (:total data)
:client (or (:client data) active-client)}]]
[form-builder/error-notification]
[:div {:style {:margin-bottom "1em"}}]
[:div.columns
(when-not exists?
[:div.column
[drop-down {:header [:button.button.is-primary-two.is-medium.is-fullwidth {:aria-haspopup true
:type "button"
:on-click (dispatch-event [::events/toggle-menu ::add-and-print-invoice ])
:disabled (or (status/disabled-for status)
(not can-submit?))
:class (status/class-for status)}
"Pay "
[:span " "]
[:span.icon [:i.fa.fa-angle-down {:aria-hidden "true"}]]]
:class "is-fullwidth"
:id ::add-and-print-invoice}
[:div
(list
(for [{:keys [id name type]} (->> (:bank-accounts (:client data)) (filter :visible) (sort-by :sort-order))]
(if (= :cash type)
^{:key id} [:a.dropdown-item {:on-click (dispatch-event [::save-requested [::add-and-print id :cash]])} "With cash"]
(list
^{:key (str id "-check")} [:a.dropdown-item {:on-click (dispatch-event [::save-requested [::add-and-print id :check]])} "Print checks from " name]
^{:key (str id "-debit")} [:a.dropdown-item {:on-click (dispatch-event [::save-requested [::add-and-print id :debit]])} "Debit from " name]))))]]])
[:div.column
[form-builder/submit-button {:class ["is-fullwidth"]}
"Save"]]]])])
(defn form [_]
(r/create-class
{:display-name "invoice-form"
:component-did-mount #(re-frame/dispatch [::mounted])
:component-will-unmount #(re-frame/dispatch [::unmounted])
:reagent-render (fn [p]
[form-content p])}))

View File

@@ -1,127 +0,0 @@
(ns auto-ap.views.pages.invoices.handwritten-checks
(:require
[auto-ap.forms :as forms]
[auto-ap.forms.builder :as form-builder]
[auto-ap.status :as status]
[auto-ap.subs :as subs]
[auto-ap.views.components.modal :as modal]
[auto-ap.views.components.money-field :refer [money-field]]
[auto-ap.views.pages.invoices.common
:refer [does-amount-exceed-outstanding? invoice-read]]
[auto-ap.views.utils
:refer [date-picker dispatch-event with-user]]
[clojure.string :as str]
[re-frame.core :as re-frame]
[auto-ap.views.components :as com]
[malli.core :as m]
[auto-ap.schema :as schema]))
(def handwritten-check-schema
(m/schema
[:map
[:bank-account-id schema/not-empty-string]
[:date schema/date]
[:check-number [:int {:min 1000 :max 99999}]]
]))
(defn form []
(let [real-bank-accounts @(re-frame/subscribe [::subs/real-bank-accounts])
{:keys [data]} @(re-frame/subscribe [::forms/form ::form])]
[form-builder/builder {:submit-event [::save]
:can-submit [::can-submit]
:id ::form
:schema handwritten-check-schema}
[form-builder/field-v2 {:field :bank-account-id}
"Pay using"
[com/select-field {:options (for [{:keys [id name]} real-bank-accounts]
[id name])
:allow-nil? true}]]
[form-builder/field-v2 {:field :date}
"Date"
[date-picker {:type "date"
:output :cljs-date}]]
[form-builder/field-v2 {:field :check-number}
"Check number"
[com/number-input {:style {:width "8em"}}]]
[:table.table.is-fullwidth
[:thead
[:tr
[:th "Invoice ID"]
[:th {:style {"width" "14em"}} "Payment"]]]
[:tbody
(doall
(for [{:keys [invoice-number id]} (:invoices data)]
^{:key id}
[:tr
[:td invoice-number]
[:td
[form-builder/raw-field-v2 {:field [:invoice-amounts id :amount]}
[money-field {:style {:max-width "9em"}}]]]]))]]
[form-builder/hidden-submit-button]]))
(re-frame/reg-sub
::can-submit
:<- [::forms/form ::form]
(fn [{ {:keys [check-number date invoices invoice-amounts]} :data}]
(boolean (cond (seq (filter
(fn [{:keys [id outstanding-balance]}]
(does-amount-exceed-outstanding? (get-in invoice-amounts [id :amount]) outstanding-balance ))
invoices))
false
:else
(and (not (str/blank? check-number))
(not (str/blank? date)))))))
(re-frame/reg-event-fx
::show
(fn [{:keys [db]} [_ invoices]]
{:dispatch [::modal/modal-requested {:title "Handwrite checks"
:body [form]
:confirm {:value "Save handwritten check"
:status-from [::status/single ::form]
:class "is-primary"
:on-click (dispatch-event [::save])
:can-submit [::can-submit]
:close-event [::status/completed ::form]}}]
:db (-> db
(forms/start-form ::form
{:bank-account-id nil
:invoices invoices
:invoice-amounts (into {}
(map (fn [i] [(:id i)
{:amount (:outstanding-balance i)}])
invoices))}))}))
(re-frame/reg-event-fx
::save
[with-user (forms/in-form ::form)]
(fn [{:keys [db user]} _]
(let [{:keys [date invoices invoice-amounts check-number bank-account-id]} (:data db)]
{:graphql
{:token user
:owns-state {:single ::form}
:query-obj {:venia/operation {:operation/type :mutation
:operation/name "AddHandwrittenCheck"}
:venia/queries [{:query/data [:add-handwritten-check
{:date date
:invoice_payments (map (fn [x]
{:invoice-id (:id x)
:amount (get-in invoice-amounts [(:id x) :amount])})
invoices)
:check-number check-number
:bank-account-id bank-account-id}
[[:invoices invoice-read]]]}]}
:on-success (fn [result]
[::succeeded (:invoices (:add-handwritten-check result))])}})))
(re-frame/reg-event-fx
::succeeded
[(forms/triggers-stop ::form)]
(fn [_ _]
{:dispatch [::modal/modal-closed]}))

View File

@@ -1,6 +1,8 @@
(ns auto-ap.views.pages.ledger.table
(:require [auto-ap.events :as events]
[auto-ap.routes :as routes]
[auto-ap.ssr-routes :as ssr-routes]
[auto-ap.routes.invoice :as i-route]
[auto-ap.subs :as subs]
[auto-ap.views.components.buttons :as buttons]
[auto-ap.views.components.dropdown
@@ -55,7 +57,7 @@
"Invoice"]
[:td
[buttons/fa-icon {:icon "fa-external-link"
:href (str (bidi/path-for routes/routes :invoices )
:href (str (bidi/path-for ssr-routes/only-routes ::i-route/all-page )
"?"
(url/map->query {:exact-match-id original-entity}))}]]]
(= "transaction" source)

View File

@@ -1,168 +0,0 @@
(ns auto-ap.views.pages.payments
(:require
[auto-ap.effects.forward :as forward]
[auto-ap.status :as status]
[auto-ap.subs :as subs]
[auto-ap.views.components.layouts :refer [side-bar-layout]]
[auto-ap.views.components.modal :as modal]
[auto-ap.views.pages.data-page :as data-page]
[auto-ap.views.pages.payments.side-bar :as side-bar]
[auto-ap.views.pages.payments.table :as table]
[auto-ap.views.utils :refer [dispatch-event nf with-user date->str standard]]
[cljs-time.core :as time]
[clojure.set :as set]
[goog.string :as gstring]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[vimsical.re-frame.fx.track :as track]))
(defn data-params->query-params [params]
(if (:exact-match-id params)
{:exact-match-id (some-> (:exact-match-id params) str)}
{:start (:start params 0)
:per-page (:per-page params)
:sort (:sort params)
:vendor-id (:id (:vendor params))
:payment-type (:payment-type params)
:status (:status params)
:date-range (:date-range params)
:amount-gte (:amount-gte (:amount-range params))
:amount-lte (:amount-lte (:amount-range params))
:check-number-like (str (:check-number-like params))
:invoice-number (:invoice-number params)}))
(re-frame/reg-event-fx
::params-change
[with-user]
(fn [{:keys [user]}[_ params]]
{:graphql {:token user
:owns-state {:single [::data-page/page ::page]}
:query-obj {:venia/queries [[:payment_page
{:filters (data-params->query-params params)}
[[:payments [:id :status :amount :type :check_number :s3_url
[:bank-account [:name]]
:date [:vendor [:name :id]] [:client [:name :id]]
[:invoices [:invoice-id [:invoice [:invoice-number :id]]
:amount]]
[:transaction [:id :date]]]]
:total
:start
:end]]]}
:on-success (fn [result]
(let [result (set/rename-keys (first (:payment-page result))
{:payments :data})]
[::data-page/received ::page result]))}}))
(re-frame/reg-event-fx
::unmounted
(fn [{:keys [db]} _]
{:dispatch [::data-page/dispose ::page]
:db (-> db (status/reset-multi ::table/void))
::track/dispose {:id ::params}
::forward/dispose {:id ::page}}))
(re-frame/reg-event-fx
::mounted
(fn [{:keys [db]} _]
{:db (assoc-in db [::data-page/settled-filters ::page :date-range] {:start (date->str (time/plus (time/now) (time/months -1))
standard)})
::track/register {:id ::params
:subscription [::data-page/params ::page]
:event-fn (fn [params]
[::params-change params])}
::forward/register {:id ::page
:events #{::table/payment-voided}
:event-fn (fn [[_ {:keys [void-payment]}]]
[::data-page/updated-entity ::page (assoc void-payment :class "live-removed")])}}))
(re-frame/reg-event-fx
::voided-selected
(fn [_ _]
{:dispatch-n [[::modal/modal-closed]
[::params-change @(re-frame/subscribe [::data-page/params ::page])]
[::data-page/reset-checked ::page]]}))
(re-frame/reg-event-fx
::void-selected
(fn [cofx [_ which]]
(let [checked-params (get which "header")
specific-invoices (map :id (vals (dissoc which "header")))]
{:graphql {:token (-> cofx :db :user)
:owns-state {:single ::void-selected}
:query-obj
{:venia/operation {:operation/type :mutation
:operation/name "VoidPayments"}
:venia/queries [{:query/data
[:void-payments
{:filters (some-> checked-params data-params->query-params)
:ids specific-invoices}
[:message]]}]}
:on-success (fn [_]
[::voided-selected])}})))
(re-frame/reg-event-fx
::void-selected-requested
(fn [_ [_ which]]
(let [to-delete (if (get which "header")
"all visible payments"
(str (count which) " payments"))]
{:dispatch [::modal/modal-requested {:title "Confirmation"
:body [:div (str "Are you sure you want to void " to-delete "?")]
:cancel? true
:confirm {:value "Void"
:class "is-danger"
:status-from [::status/single ::void-selected]
:on-click (dispatch-event [::void-selected which] )}
:close-event [::status/completed ::void-selected]}]})))
(defn void-selected-button []
(let [status @(re-frame/subscribe [::status/single ::void-selected])
checked-payments @(re-frame/subscribe [::data-page/checked ::page])
is-admin? @(re-frame/subscribe [::subs/is-admin?])]
(when is-admin?
[:button.button.is-danger {:on-click (dispatch-event [::void-selected-requested checked-payments])
:class (status/class-for status)
:disabled (or (status/disabled-for status)
(not (seq checked-payments)))}
" Void"])))
(defn action-buttons []
(let [checked-payments @(re-frame/subscribe [::data-page/checked ::page])]
[:<>
[:div.level-item
(into [:div.tags ] (map (fn [[z {:keys [id check-number type amount]}]]
(if (= z "header")
[:span.tag.is-medium "All visible payments"
[:button.delete.is-small {:on-click
(dispatch-event [::data-page/remove-check ::page z])}]]
[:span.tag.is-medium (cond
(= :cash type) (gstring/format "Cash (%s)" (nf amount ))
(= :debit type) (gstring/format "Debit (%s)" (nf amount ))
:else (gstring/format "Check #%d (%s)" check-number (nf amount )))
[:button.delete.is-small {:on-click
(dispatch-event [::data-page/remove-check ::page id])}]]))
checked-payments))]
[:div.level-item
[:div.buttons
[void-selected-button]]]]))
(defn content []
[:div
[:h1.title "Payments"]
[status/status-notification {:statuses [[::status/last-multi ::table/void]]}]
[table/table {:id :payments
:data-page ::page
:action-buttons [action-buttons]}]])
(defn payments-page []
(reagent/create-class
{:display-name "payments-page"
:component-will-unmount #(re-frame/dispatch-sync [::unmounted])
:component-did-mount #(re-frame/dispatch [::mounted])
:reagent-render
(fn []
[side-bar-layout {:side-bar [side-bar/side-bar {:data-page ::page}]
:main [content]}])}))

View File

@@ -1,98 +0,0 @@
(ns auto-ap.views.pages.payments.side-bar
(:require
[auto-ap.subs :as subs]
[auto-ap.views.components.date-range-filter :refer [date-range-filter]]
[auto-ap.views.components.number-filter :refer [number-filter]]
[auto-ap.views.components.typeahead.vendor
:refer [search-backed-typeahead]]
[auto-ap.views.pages.data-page :as data-page]
[auto-ap.views.utils :refer [dispatch-event dispatch-value-change]]
[re-frame.core :as re-frame]))
(defn side-bar [{:keys [data-page]}]
(let [_ @(re-frame/subscribe [::subs/active-page])
_ @(re-frame/subscribe [::subs/user])]
[:div
[:div
[:p.menu-label "Vendor"]
[:div
[search-backed-typeahead {:search-query (fn [i]
[:search_vendor
{:query i}
[:name :id]])
:type "typeahead-v3"
:on-change #(re-frame/dispatch [::data-page/filter-changed data-page :vendor %])
:value @(re-frame/subscribe [::data-page/filter data-page :vendor])}]]
[:p.menu-label "Date Range"]
[:div
[date-range-filter
{:on-change-event [::data-page/filter-changed data-page :date-range]
:value @(re-frame/subscribe [::data-page/filter data-page :date-range])}]]
[:p.menu-label "Amount"]
[:div
[number-filter
{:on-change-event [::data-page/filter-changed data-page :amount-range]
:value @(re-frame/subscribe [::data-page/filter data-page :amount-range])}]]
[:p.menu-label "Check #"]
[:div.field
[:div.control [:input.input {:placeholder "10001"
:value @(re-frame/subscribe [::data-page/filter data-page :check-number-like])
:on-change (dispatch-value-change [::data-page/filter-changed data-page :check-number-like])} ]]]
[:p.menu-label "Invoice #"]
[:div.field
[:div.control [:input.input {:placeholder "SJ-12345"
:value @(re-frame/subscribe [::data-page/filter data-page :invoice-number])
:on-change (dispatch-value-change [::data-page/filter-changed data-page :invoice-number])} ]]]
[:p.menu-label "Payment Type"]
[:div.field.has-addons
[:p.control [:a.button.is-small {:on-click
(dispatch-event [::data-page/filter-changed data-page :payment-type :cash])
:class (when (= :cash @(re-frame/subscribe [::data-page/filter data-page :payment-type]))
["is-selected" "is-success"])}
"Cash" ]]
[:p.control [:a.button.is-small {:on-click
(dispatch-event [::data-page/filter-changed data-page :payment-type :check])
:class (when (= :check @(re-frame/subscribe [::data-page/filter data-page :payment-type]))
["is-selected" "is-success"])}
"Check" ]]
[:p.control [:a.button.is-small {:on-click
(dispatch-event [::data-page/filter-changed data-page :payment-type :debit])
:class (when (= :debit @(re-frame/subscribe [::data-page/filter data-page :payment-type]))
["is-selected" "is-success"])}
"Debit"]]
[:p.control [:a.button.is-small {:on-click
(dispatch-event [::data-page/filter-changed data-page :payment-type nil])}
"All"]]]
[:p.menu-label "Status"]
[:div.field.has-addons
[:p.control [:a.button.is-small {:on-click
(dispatch-event [::data-page/filter-changed data-page :status :voided])
:class (when (= :voided @(re-frame/subscribe [::data-page/filter data-page :status]))
["is-selected" "is-success"])}
"Voided" ]]
[:p.control [:a.button.is-small {:on-click
(dispatch-event [::data-page/filter-changed data-page :status :pending])
:class (when (= :pending @(re-frame/subscribe [::data-page/filter data-page :status]))
["is-selected" "is-success"])}
"Pending" ]]
[:p.control [:a.button.is-small {:on-click
(dispatch-event [::data-page/filter-changed data-page :status :cleared])
:class (when (= :cleared @(re-frame/subscribe [::data-page/filter data-page :status]))
["is-selected" "is-success"])}
"Cleared"]]
[:p.control [:a.button.is-small {:on-click
(dispatch-event [::data-page/filter-changed data-page :status nil])}
"All"]]]
(when-let [exact-match-id @(re-frame/subscribe [::data-page/filter data-page :exact-match-id])]
[:div
[:p.menu-label "Specific Payment"]
[:span.tag.is-medium exact-match-id " "
[:button.delete.is-small {:on-click
(dispatch-event [::data-page/filter-changed data-page :exact-match-id nil])}]]])]]))

View File

@@ -1,133 +0,0 @@
(ns auto-ap.views.pages.payments.table
(:require [auto-ap.events :as events]
[auto-ap.routes :as routes]
[auto-ap.status :as status]
[auto-ap.subs :as subs]
[auto-ap.views.components.buttons :as buttons]
[auto-ap.views.components.dropdown
:refer
[drop-down drop-down-contents]]
[auto-ap.views.components.grid :as grid]
[auto-ap.views.pages.data-page :as data-page]
[auto-ap.views.utils
:refer
[date->str dispatch-event-with-propagation nf pretty]]
[bidi.bidi :as bidi]
[cemerick.url :as url]
[goog.string :as gstring]
[re-frame.core :as re-frame]))
(re-frame/reg-event-fx
::void-check
(fn [{:keys [db]} [_ payment]]
{:graphql
{:token (-> db :user)
:owns-state {:multi ::void
:which (:id payment)}
:query-obj {:venia/operation {:operation/type :mutation
:operation/name "VoidPayment"}
:venia/queries [{:query/data [:void-payment
{:payment-id (:id payment)}
[:id :status [:bank-account [:name]] :amount :check_number :s3_url :date [:vendor [:name :id]] [:client [:name :id]]
[:invoices [:invoice-id]]]]}]}
:on-success [::payment-voided]}}))
(re-frame/reg-event-db
::payment-voided
(fn [db [_ _]]
db))
(defn row [{check :check
selected-client :selected-client
states :states
}]
(let [{:keys [client s3-url bank-account type check-number date amount id vendor status invoices transaction] :as check} check]
[grid/row {:class (:class check) :id id :entity check}
(when-not selected-client
[grid/cell {} (:name client)])
[grid/cell {} (:name vendor)]
[grid/cell {} (:name bank-account)]
[grid/cell {} (cond
(= :cash type) "Cash"
(= :debit type) "Debit"
:else (if s3-url
[:a.button {:href s3-url :target "_new"} [:span [:span.icon [:i.fa.fa-share-square-o]]
[:span (str " " check-number )]]]
check-number))]
[grid/cell {} (date->str date) ]
[grid/cell {:class "has-text-right"} (nf amount )]
[grid/cell {} status]
[grid/button-cell {}
[:div.buttons
(when (and (seq invoices) (not= :voided status))
[drop-down {:id [::links id]
:is-right? true
:header [buttons/fa-icon {:class "badge"
:on-click (dispatch-event-with-propagation [::events/toggle-menu [::links id]])
:data-badge (str (cond-> (clojure.core/count invoices)
transaction inc))
:icon "fa-paperclip"}]}
[drop-down-contents
[:div.dropdown-item
[:table.table.grid.compact
[:tbody
(for [invoice invoices]
^{:key (:id invoice)}
[:tr
[:td
"Invoice " (:invoice-number (:invoice invoice))
]
[:td (gstring/format "$%.2f" (:amount invoice) )]
[:td
[buttons/fa-icon {:icon "fa-external-link"
:href (str (bidi/path-for routes/routes :invoices )
"?"
(url/map->query {:exact-match-id (:id (:invoice invoice))}))}]]])
(when transaction
[:tr
[:td
"Transaction"]
[:td (date->str (:date transaction) pretty)]
[:td
[buttons/fa-icon {:icon "fa-external-link"
:href (str (bidi/path-for routes/routes :transactions )
"?"
(url/map->query {:exact-match-id (:id transaction)}))}]]])]]]]])
[:span {:style {:margin-left "1em"}}]
(when (and (or (not= :cleared status)
(= :balance-credit type))
(not= :voided status)
(not transaction))
[buttons/sl-icon {:event [::void-check check] :icon :icon-bin-2
:class (status/class-for (get states (:id check)))}])]]]))
(defn table [{:keys [data-page action-buttons]}]
(let [selected-client @(re-frame/subscribe [::subs/client])
{:keys [data params]} @(re-frame/subscribe [::data-page/page data-page])
states @(re-frame/subscribe [::status/multi ::void])]
[grid/grid {:data-page data-page
:check-boxes? true
:column-count (if selected-client 7 8)}
[grid/controls (assoc data :action-buttons action-buttons) data]
[grid/table {:fullwidth true}
[grid/header {}
[grid/row {:id "header"
:entity params}
(when-not selected-client
[grid/sortable-header-cell {:sort-key "client" :sort-name "Client"} "Client"])
[grid/sortable-header-cell {:sort-key "vendor" :sort-name "Vendor"} "Vendor"]
[grid/sortable-header-cell {:sort-key "bank-account" :sort-name "Bank Account"} "Bank Account"]
[grid/sortable-header-cell {:sort-key "check-number" :sort-name "Check Number"} "Check Number"]
[grid/sortable-header-cell {:sort-key "date" :sort-name "Date" :style {:width "8em"}} "Date"]
[grid/sortable-header-cell {:sort-key "amount" :sort-name "Amount" :class "has-text-right" :style {:width "8em"}} "Amount"]
[grid/sortable-header-cell {:sort-key "status" :sort-name "Status" :style {:width "7em"}} "Status"]
[grid/header-cell {:style {:width "12em"}}]]]
[grid/body
(for [check (:data data)]
^{:key (:id check)}
[row {:check check
:selected-client selected-client
:states states}])]]
[grid/bottom-paginator data]]))

View File

@@ -1,342 +0,0 @@
(ns auto-ap.views.pages.unpaid-invoices
(:require
[auto-ap.effects.forward :as forward]
[auto-ap.events :as events]
[auto-ap.forms :as forms]
[auto-ap.status :as status]
[auto-ap.subs :as subs]
[auto-ap.views.components.buttons :as buttons]
[auto-ap.views.pages.invoices.bulk-change :as bulk-change]
[auto-ap.views.components.dropdown :refer [drop-down]]
[auto-ap.views.components.expense-accounts-dialog
:as expense-accounts-dialog]
[auto-ap.views.components.invoice-table :as table]
[auto-ap.views.components.invoices.side-bar
:as side-bar
:refer [invoices-side-bar]]
[auto-ap.views.components.layouts
:refer [appearing-side-bar side-bar-layout]]
[auto-ap.views.components.modal :as modal]
[auto-ap.views.pages.data-page :as data-page]
[auto-ap.views.pages.invoices.advanced-print-checks
:as advanced-print-checks]
[auto-ap.views.pages.invoices.common :refer [invoice-read]]
[auto-ap.views.pages.invoices.form :as form]
[auto-ap.views.pages.invoices.handwritten-checks :as handwritten-checks]
[auto-ap.views.utils
:refer [dispatch-event dispatch-event-with-propagation with-user date->str standard]]
[clojure.set :as set]
[clojure.string :as str]
[goog.string :as gstring]
[re-frame.core :as re-frame]
[reagent.core :as r]
[vimsical.re-frame.cofx.inject :as inject]
[vimsical.re-frame.fx.track :as track]
[cljs-time.core :as time]))
(re-frame/reg-event-fx
::params-change
[with-user]
(fn [{:keys [user]} [_ params]]
(try
{:graphql {:token user
:owns-state {:single [::data-page/page :invoices]}
:query-obj (table/query params )
:on-success (fn [result]
(let [result (set/rename-keys (first (:invoice-page result))
{:invoices :data})]
[::data-page/received :invoices result]))}}
(catch js/Error e
(println "Warning" e)))))
(re-frame/reg-event-fx
::unmounted
(fn [_ _]
{:dispatch-n [[::data-page/dispose :invoices]
[::forms/form-closing ::form/form]]
::forward/dispose [{:id ::updated}
{:id ::checks-printed}]
::track/dispose [{:id ::params}]}))
(re-frame/reg-event-fx
::mounted
(fn [{:keys [db]} _]
{::track/register [{:id ::params
:subscription [::data-page/params :invoices]
:event-fn (fn [params]
[::params-change params])}]
::forward/register [{:id ::updated
:events #{::table/invoice-updated ::form/updated ::expense-accounts-dialog/updated}
:event-fn (fn [[_ invoice]]
[::data-page/updated-entity :invoices invoice])}
{:id ::checks-printed
:events #{::form/checks-printed ::advanced-print-checks/checks-printed ::handwritten-checks/succeeded}
:event-fn (fn [[_ invoices pdf-url]]
[::checks-printed invoices pdf-url])}]
:db (assoc-in db [::data-page/settled-filters :invoices :date-range] {:start (date->str (time/plus (time/now) (time/months -6))
standard)})}))
(defn print-checks-query [invoice-payments bank-account-id type client-id]
{:venia/operation {:operation/type :mutation
:operation/name "PrintChecks"}
:venia/queries [[:print-checks
{:invoice_payments invoice-payments
:type type
:bank_account_id bank-account-id
:client_id client-id}
[[:invoices invoice-read]
:pdf_url]]]})
(re-frame/reg-event-fx
::print-checks
(fn [{:keys [db]} [_ bank-account-id type]]
{:graphql
{:token (-> db :user)
:owns-state {:single ::print-checks}
:query-obj (print-checks-query (->> @(re-frame/subscribe [::data-page/checked :invoices])
(vals )
(filter (fn [{:keys [id outstanding-balance] }]
(and id outstanding-balance)))
(map (fn [{:keys [id outstanding-balance] }]
{:invoice-id id
:amount outstanding-balance})))
bank-account-id
type
(:client db))
:on-success (fn [result]
[::checks-printed
(:invoices (:print-checks result))
(:pdf-url (:print-checks result))])}}))
(re-frame/reg-event-fx
::pay-invoices-from-balance
[with-user (re-frame/inject-cofx ::inject/sub [::data-page/checked :invoices])]
(fn [{:keys [db user] ::data-page/keys [checked]} _]
{:graphql
{:token user
:owns-state {:single ::print-checks}
:query-obj {:venia/operation {:operation/type :mutation
:operation/name "PayInvoicesFromBalance"}
:venia/queries [[:pay-invoices-from-balance
{:invoices (->> checked
(vals )
(filter (fn [{:keys [id outstanding-balance] }]
(and id outstanding-balance)))
(map :id))
:client_id (:client db)}
[[:invoices invoice-read]
:pdf_url]]]}
:on-success (fn [result]
[::checks-printed
(:invoices (:pay-invoices-from-balance result))
nil])}}))
(re-frame/reg-event-fx
::checks-printed
(fn [_ [_ invoices pdf-url]]
{:dispatch-n (cond->> [[::data-page/reset-checked :invoices]]
true (into (mapv
(fn [i]
[::data-page/updated-entity :invoices i])
invoices))
pdf-url (into [[::modal/modal-requested {:title "Your checks are ready!"
:body [:div
[:div "Click " [:a {:href pdf-url :target "_new"} "here"] " to print them."]
[:div [:em "Remember to turn off all scaling and margins."]]]}]]))}))
(re-frame/reg-event-fx
::new-invoice-clicked
(fn [_ _]
{:dispatch [::form/adding {:client @(re-frame/subscribe [::subs/client])
:status :unpaid
#_#_:date (date->str (c/now) standard)
:location (first (:locations @(re-frame/subscribe [::subs/client])))}]}))
(re-frame/reg-event-fx
::voided-selected
(fn [_ [_]]
{:dispatch-n [[::modal/modal-closed]
[::params-change @(re-frame/subscribe [::data-page/params ::page])]
[::data-page/reset-checked :invoices]]}))
(re-frame/reg-event-fx
::void-selected
(fn [cofx [_ which]]
(let [checked-params (get which "header")
specific-invoices (map :id (vals (dissoc which "header")))]
{:graphql {:token (-> cofx :db :user)
:owns-state {:single ::void-selected}
:query-obj
{:venia/operation {:operation/type :mutation
:operation/name "VoidInvoices"}
:venia/queries [{:query/data
[:void-invoices
{:filters (some-> checked-params table/data-params->query-params)
:ids specific-invoices}
[:message]]}]}
:on-success (fn [_]
[::voided-selected])}})))
(re-frame/reg-event-fx
::void-selected-requested
(fn [_ [_ which]]
(let [to-delete (if (get which "header")
"all visible invoices"
(str (count which) " invoices"))]
{:dispatch [::modal/modal-requested {:title "Confirmation"
:body [:div (str "Are you sure you want to void " to-delete "?")]
:cancel? true
:confirm {:value "Void"
:class "is-danger"
:status-from [::status/single ::void-selected]
:on-click (dispatch-event [::void-selected which] )}
:close-event [::status/completed ::void-selected]}]})))
(defn void-selected-button []
(let [status @(re-frame/subscribe [::status/single ::void-selected])
checked-invoices @(re-frame/subscribe [::data-page/checked :invoices])
is-admin? @(re-frame/subscribe [::subs/is-admin?])]
(when is-admin?
[:button.button.is-danger {:on-click (dispatch-event [::void-selected-requested checked-invoices])
:class (status/class-for status)
:disabled (or (status/disabled-for status)
(not (seq checked-invoices)))}
"Void"])))
(defn pay-button [status]
(let [current-client @(re-frame/subscribe [::subs/client])
checked-invoices @(re-frame/subscribe [::data-page/checked :invoices])
is-admin? @(re-frame/subscribe [::subs/is-admin?])
print-checks-status @(re-frame/subscribe [::status/single ::print-checks])
params @(re-frame/subscribe [::data-page/params :invoices])
{:keys [total]} @(re-frame/subscribe [::data-page/data :invoices])
]
[:div.buttons
(when is-admin?
[void-selected-button])
[buttons/new-button {:event [::new-invoice-clicked]
:name "Invoice"
:class "is-primary"}]
(when (and is-admin?
current-client)
[buttons/event-button {:event [::bulk-change/bulk-change-requested checked-invoices params total]
:name "Bulk Edit"
:class "is-secondary"
:disabled (not (seq checked-invoices))}])
(when (and current-client
(= :unpaid status))
(let [balance (->> checked-invoices
vals
(map (comp js/parseFloat :outstanding-balance))
(reduce + 0)
)]
[drop-down {:header [:button.button.is-primary {:aria-haspopup true
:on-click (dispatch-event [::events/toggle-menu ::print-checks ])
:disabled (or (status/disabled-for print-checks-status) (not (seq checked-invoices)))
:class (status/class-for @(re-frame/subscribe [::status/single ::print-checks]))}
"Pay "
(when (> (count checked-invoices ) 0)
(str
(count checked-invoices)
" invoices "
"(" (gstring/format "$%.2f" balance ) ")"))
[:span " "]
[:span.icon.is-small [:i.fa.fa-angle-down {:aria-hidden "true"}]]]
:id ::print-checks
:is-right? true}
[:div
(list
(for [{:keys [id name type]} (->> (:bank-accounts current-client) (filter :visible) (sort-by :sort-order))]
(if (= :cash type)
^{:key id} [:a.dropdown-item {:on-click (dispatch-event [::print-checks id :cash])
:disabled (status/disabled-for print-checks-status)} "With cash"]
(if (> balance 0.001)
(list
^{:key (str id "-check")} [:a.dropdown-item {:on-click (dispatch-event-with-propagation [::print-checks id :check])
:disabled (status/disabled-for print-checks-status)} "Print checks from " name]
^{:key (str id "-debit")} [:a.dropdown-item {:on-click (dispatch-event-with-propagation [::print-checks id :debit])
:disabled (status/disabled-for print-checks-status)} "Debit from " name])
(list
^{:key (str id "-credit")} [:a.dropdown-item {:on-click (dispatch-event-with-propagation [::print-checks id :credit])
:disabled (status/disabled-for print-checks-status)} "Credit from " name]
))))
^{:key "advanced-divider"} [:hr.dropdown-divider]
(when (and (> (count checked-invoices) 1)
(= 1 (count (set (map (comp :id :vendor) (vals checked-invoices)))))
(< balance 0.001))
^{:key (str "balance-credit")} [:a.dropdown-item {:on-click (dispatch-event-with-propagation [::pay-invoices-from-balance])
:disabled (status/disabled-for print-checks-status)} "Pay invoices using balance "])
(when (and (= 1 (count (set (map (comp :id :vendor) (vals checked-invoices)))))
(> balance 0.001))
^{:key "handwritten"} [:a.dropdown-item {:on-click (dispatch-event-with-propagation [::handwritten-checks/show (vals checked-invoices)])
:disabled (status/disabled-for print-checks-status)} "Handwritten Check..."])
(when (> balance 0.001)
^{:key "advanced"} [:a.dropdown-item {:on-click (dispatch-event-with-propagation [::advanced-print-checks/show (vals checked-invoices)])
:disabled (status/disabled-for print-checks-status)} "Advanced..."]))]]))]))
(defn unpaid-invoices-content [{:keys [status]}]
(let [page @(re-frame/subscribe [::data-page/page :invoices])
_ @(re-frame/subscribe [::subs/client])
_ @(re-frame/subscribe [::subs/selected-clients])]
[:div
[:h1.title (str (str/capitalize (name (or status :all))) " invoices")]
[status/status-notification {:statuses [[::status/single ::print-checks]
[::status/last-multi ::table/void]
[::status/last-multi ::table/unvoid]]}]
[table/invoice-table {:id (:id page)
:data-page :invoices
:check-boxes true
:checkable-fn (fn [i] (not (:scheduled-payment i)))
:actions #{:edit :void :expense-accounts}
:action-buttons [pay-button status]}]]))
(defn layout [params]
(let [{invoice-bar-active? :active?} @(re-frame/subscribe [::forms/form ::form/form])]
[side-bar-layout {:side-bar [invoices-side-bar {:data-page :invoices}]
:main [unpaid-invoices-content params]
:right-side-bar [appearing-side-bar {:visible? invoice-bar-active?} [form/form {}]]}]))
(defn unpaid-invoices-page []
(r/create-class
{:display-name "unpaid-invoices-page"
:component-will-unmount #(re-frame/dispatch-sync [::unmounted])
:component-did-mount #(re-frame/dispatch [::mounted])
:reagent-render layout}))
(defn paid-invoices-page []
(r/create-class
{:display-name "paid-invoices-page"
:component-will-unmount #(re-frame/dispatch-sync [::unmounted])
:component-did-mount #(re-frame/dispatch [::mounted])
:reagent-render layout}))
(defn voided-invoices-page []
(r/create-class
{:display-name "voided-invoices-page"
:component-will-unmount #(re-frame/dispatch-sync [::unmounted])
:component-did-mount #(re-frame/dispatch [::mounted])
:reagent-render layout}))
(defn all-invoices-page []
(r/create-class
{:display-name "all-invoices-page"
:component-will-unmount #(re-frame/dispatch-sync [::unmounted])
:component-did-mount #(re-frame/dispatch [::mounted])
:reagent-render layout}))