fully removes old invoice experience
This commit is contained in:
@@ -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)))
|
||||
|
||||
@@ -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")]]]
|
||||
|
||||
@@ -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]]))
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}))}))
|
||||
@@ -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]]))
|
||||
@@ -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})
|
||||
|
||||
@@ -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 ]}])
|
||||
|
||||
|
||||
@@ -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]}))
|
||||
|
||||
@@ -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)])}))})))
|
||||
@@ -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]]]]])
|
||||
@@ -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])}))
|
||||
@@ -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]}))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]}])}))
|
||||
@@ -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])}]]])]]))
|
||||
@@ -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]]))
|
||||
@@ -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}))
|
||||
Reference in New Issue
Block a user