diff --git a/src/clj/auto_ap/ssr/invoice/glimpse.clj b/src/clj/auto_ap/ssr/invoice/glimpse.clj index 46af3522..85572188 100644 --- a/src/clj/auto_ap/ssr/invoice/glimpse.clj +++ b/src/clj/auto_ap/ssr/invoice/glimpse.clj @@ -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))) diff --git a/src/clj/auto_ap/ssr/invoice/import.clj b/src/clj/auto_ap/ssr/invoice/import.clj index 4532cb73..604d8896 100644 --- a/src/clj/auto_ap/ssr/invoice/import.clj +++ b/src/clj/auto_ap/ssr/invoice/import.clj @@ -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")]]] diff --git a/src/clj/auto_ap/ssr/invoices.clj b/src/clj/auto_ap/ssr/invoices.clj index 5623a610..4acac68d 100644 --- a/src/clj/auto_ap/ssr/invoices.clj +++ b/src/clj/auto_ap/ssr/invoices.clj @@ -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]])) diff --git a/src/clj/user.fiddle b/src/clj/user.fiddle index 7b82fb13..81cfe228 100644 --- a/src/clj/user.fiddle +++ b/src/clj/user.fiddle @@ -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 + diff --git a/src/cljc/auto_ap/client_routes.cljc b/src/cljc/auto_ap/client_routes.cljc index 0a40e437..cf899ea5 100644 --- a/src/cljc/auto_ap/client_routes.cljc +++ b/src/cljc/auto_ap/client_routes.cljc @@ -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 diff --git a/src/cljc/auto_ap/routes/invoice.cljc b/src/cljc/auto_ap/routes/invoice.cljc index 294cd0a0..03cc19a2 100644 --- a/src/cljc/auto_ap/routes/invoice.cljc +++ b/src/cljc/auto_ap/routes/invoice.cljc @@ -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}) diff --git a/src/cljc/auto_ap/ssr_routes.cljc b/src/cljc/auto_ap/ssr_routes.cljc index d6654b39..c324061c 100644 --- a/src/cljc/auto_ap/ssr_routes.cljc +++ b/src/cljc/auto_ap/ssr_routes.cljc @@ -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} diff --git a/src/cljs/auto_ap/views/components/expense_accounts_dialog.cljs b/src/cljs/auto_ap/views/components/expense_accounts_dialog.cljs deleted file mode 100644 index 1497471a..00000000 --- a/src/cljs/auto_ap/views/components/expense_accounts_dialog.cljs +++ /dev/null @@ -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}))})) diff --git a/src/cljs/auto_ap/views/components/invoice_table.cljs b/src/cljs/auto_ap/views/components/invoice_table.cljs deleted file mode 100644 index 2a35bbd2..00000000 --- a/src/cljs/auto_ap/views/components/invoice_table.cljs +++ /dev/null @@ -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]])) diff --git a/src/cljs/auto_ap/views/main.cljs b/src/cljs/auto_ap/views/main.cljs index 35bd1c17..4933dc7e 100644 --- a/src/cljs/auto_ap/views/main.cljs +++ b/src/cljs/auto_ap/views/main.cljs @@ -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}) diff --git a/src/cljs/auto_ap/views/pages/import_invoices.cljs b/src/cljs/auto_ap/views/pages/import_invoices.cljs deleted file mode 100644 index 9c851405..00000000 --- a/src/cljs/auto_ap/views/pages/import_invoices.cljs +++ /dev/null @@ -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 "
"}))) - :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 ]}]) - - diff --git a/src/cljs/auto_ap/views/pages/invoices/advanced_print_checks.cljs b/src/cljs/auto_ap/views/pages/invoices/advanced_print_checks.cljs deleted file mode 100644 index 1bbddbae..00000000 --- a/src/cljs/auto_ap/views/pages/invoices/advanced_print_checks.cljs +++ /dev/null @@ -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]})) - diff --git a/src/cljs/auto_ap/views/pages/invoices/bulk_change.cljs b/src/cljs/auto_ap/views/pages/invoices/bulk_change.cljs deleted file mode 100644 index 292e52cd..00000000 --- a/src/cljs/auto_ap/views/pages/invoices/bulk_change.cljs +++ /dev/null @@ -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)])}))}))) diff --git a/src/cljs/auto_ap/views/pages/invoices/common.cljs b/src/cljs/auto_ap/views/pages/invoices/common.cljs deleted file mode 100644 index 2121e78a..00000000 --- a/src/cljs/auto_ap/views/pages/invoices/common.cljs +++ /dev/null @@ -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]]]]]) diff --git a/src/cljs/auto_ap/views/pages/invoices/form.cljs b/src/cljs/auto_ap/views/pages/invoices/form.cljs deleted file mode 100644 index 64174d6b..00000000 --- a/src/cljs/auto_ap/views/pages/invoices/form.cljs +++ /dev/null @@ -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])})) diff --git a/src/cljs/auto_ap/views/pages/invoices/handwritten_checks.cljs b/src/cljs/auto_ap/views/pages/invoices/handwritten_checks.cljs deleted file mode 100644 index 7bec532f..00000000 --- a/src/cljs/auto_ap/views/pages/invoices/handwritten_checks.cljs +++ /dev/null @@ -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]})) - diff --git a/src/cljs/auto_ap/views/pages/ledger/table.cljs b/src/cljs/auto_ap/views/pages/ledger/table.cljs index 76973a2f..76d6576e 100644 --- a/src/cljs/auto_ap/views/pages/ledger/table.cljs +++ b/src/cljs/auto_ap/views/pages/ledger/table.cljs @@ -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) diff --git a/src/cljs/auto_ap/views/pages/payments.cljs b/src/cljs/auto_ap/views/pages/payments.cljs deleted file mode 100644 index beb244df..00000000 --- a/src/cljs/auto_ap/views/pages/payments.cljs +++ /dev/null @@ -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]}])})) diff --git a/src/cljs/auto_ap/views/pages/payments/side_bar.cljs b/src/cljs/auto_ap/views/pages/payments/side_bar.cljs deleted file mode 100644 index 2f070481..00000000 --- a/src/cljs/auto_ap/views/pages/payments/side_bar.cljs +++ /dev/null @@ -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])}]]])]])) diff --git a/src/cljs/auto_ap/views/pages/payments/table.cljs b/src/cljs/auto_ap/views/pages/payments/table.cljs deleted file mode 100644 index ecde7005..00000000 --- a/src/cljs/auto_ap/views/pages/payments/table.cljs +++ /dev/null @@ -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]])) diff --git a/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs b/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs deleted file mode 100644 index 835ac734..00000000 --- a/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs +++ /dev/null @@ -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}))