(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.cofx.inject :as inject] [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 [_ _] {: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 [_ _] {::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 ::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 [] (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.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 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])] [: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 (= status :unpaid) :checkable-fn (fn [i] (not (:scheduled-payment i))) :actions #{:edit :void :expense-accounts} :action-buttons (when (= status :unpaid) [pay-button])}]])) (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}))