Files
integreat/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs
2022-03-13 12:04:59 -07:00

304 lines
15 KiB
Clojure

(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.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]]
[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.fx.track :as track]))
(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 [{:keys [db]} _]
{:dispatch [::data-page/dispose :invoices]
::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])}]}))
(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
::checks-printed
(fn [{:keys [db]} [_ 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 [{:keys [db]} _]
{: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 [cofx [_]]
{: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 []
(let [current-client @(re-frame/subscribe [::subs/client])
checked-invoices @(re-frame/subscribe [::data-page/checked :invoices])
print-checks-status @(re-frame/subscribe [::status/single ::print-checks])]
[:div
[:div.is-pulled-right
[:div.buttons
[void-selected-button]
[buttons/new-button {:event [::new-invoice-clicked]
:name "Invoice"
:class "is-primary"}]
(when current-client
(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 number 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]))))
(when (> balance 0.001)
^{:key "advanced-divider"} [:hr.dropdown-divider])
(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..."]))]]))]]
[:div.is-pulled-right {:style {:margin-right "0.5rem"}}
(into [:div.tags ] (map (fn [[z {:keys [id invoice-number]}]]
(if (= z "header")
[:span.tag.is-medium "All visible invoices"
[:button.delete.is-small {:on-click
(dispatch-event [::data-page/remove-check :invoices z])}]]
[:span.tag.is-medium invoice-number
[:button.delete.is-small {:on-click
(dispatch-event [::data-page/remove-check :invoices id])}]]))
checked-invoices))]]))
(defn unpaid-invoices-content [{:keys [status]}]
(let [page @(re-frame/subscribe [::data-page/page :invoices])
current-client @(re-frame/subscribe [::subs/client])]
[: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]]}]
(when (= status :unpaid)
[pay-button])
[table/invoice-table {:id (:id page)
:data-page :invoices
:check-boxes (= status :unpaid)
:checkable-fn (fn [i] (not (:scheduled-payment i)))
:actions #{:edit :void :expense-accounts}}]]))
(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 [params]
(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 [params]
(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 [params]
(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 [params]
(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}))