245 lines
12 KiB
Clojure
245 lines
12 KiB
Clojure
(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.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 nf]]
|
|
[bidi.bidi :as bidi]
|
|
[cemerick.url :as url]
|
|
[cljs-time.core :as t]
|
|
[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]))
|
|
|
|
(defn query [params]
|
|
{:venia/queries [[:invoice_page
|
|
(-> params
|
|
(assoc
|
|
:client-id (:id @(re-frame/subscribe [::subs/client]))))
|
|
[[:invoices [:id :total :outstanding-balance :invoice-number :date :due :status :client-identifier :automatically-paid-when-due
|
|
[:vendor [:name :id]]
|
|
[:expense_accounts [:amount :id :location
|
|
[:account [:id ]]]]
|
|
[:client [:name :id :locations]]
|
|
[:payments [:amount :id [:payment [:id :status :amount :s3_url :check_number
|
|
[:transaction [:post_date]]]]]]]]
|
|
:outstanding
|
|
:total
|
|
:start
|
|
:end]]]})
|
|
|
|
(re-frame/reg-sub
|
|
::specific-table-params
|
|
(fn [db]
|
|
(::table-params db)))
|
|
|
|
(re-frame/reg-sub
|
|
::table-params
|
|
:<- [::specific-table-params]
|
|
:<- [::subs/query-params]
|
|
(fn [[specific-table-params query-params]]
|
|
(update (merge (select-keys query-params #{:start :sort}) specific-table-params )
|
|
:sort seq)))
|
|
|
|
(re-frame/reg-event-fx
|
|
::params-changed
|
|
[(re-frame/path [::table-params])]
|
|
(fn [{table-params :db} [_ params :as z]]
|
|
{:db (merge table-params params)}))
|
|
|
|
(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
|
|
::invoice-updated
|
|
(fn [{:keys [db]} [_ invoice]]
|
|
{:db db}))
|
|
|
|
(defn row [{:keys [invoice check-boxes checked selected-client overrides expense-event actions]}]
|
|
(let [{:keys [client payments expense-accounts invoice-number date due total outstanding-balance id vendor checkable?] :as i} invoice
|
|
accounts-by-id @(re-frame/subscribe [::subs/accounts-by-id client])
|
|
account->name #(:name (accounts-by-id (:id %)))]
|
|
[grid/row {:class (:class i) :id id :checkable? checkable?}
|
|
(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 {} (date->str date) ]
|
|
[grid/cell {}
|
|
(when due
|
|
(let [today (t/at-midnight (t/now))
|
|
due (t/at-midnight due)
|
|
due-in (if (t/after? today due)
|
|
(- (t/in-days (t/interval (t/minus due (t/days 1)) today)))
|
|
(t/in-days (t/interval today 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"} (nf outstanding-balance )]
|
|
[grid/button-cell {}
|
|
[:div.buttons
|
|
(when (seq expense-accounts)
|
|
[drop-down {:id [::expense-accounts id ]
|
|
: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 (account->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 (seq payments)
|
|
[:<>
|
|
[drop-down {:id [::payments id]
|
|
:header [buttons/sl-icon {:class "badge"
|
|
:event [::events/toggle-menu [::payments id]]
|
|
:data-badge (str (clojure.core/count payments))
|
|
:icon "icon-accounting-bill"}]}
|
|
[:div
|
|
(for [payment payments]
|
|
(if (:check-number (:payment payment))
|
|
^{:key (:id payment)}
|
|
[:a.dropdown-item {:href (str (bidi/path-for routes/routes :payments )
|
|
"?"
|
|
(url/map->query {:check-number-like (:check-number (:payment payment))}))
|
|
:target "_new"}
|
|
[:i.fa.fa-money-check]
|
|
[:span.icon [:i.fa.fa-money]]
|
|
(str " " (:check-number (:payment payment)) " (" (gstring/format "$%.2f" (:amount payment) ) ")")]
|
|
|
|
^{:key (:id payment)}
|
|
[:span.dropdown-item [:span.icon {:class [(when (= :cleared (:status (:payment payment)))
|
|
"has-text-success")]}
|
|
[:i.fa.fa-money]] (str " " (:check-number (:payment payment)) " (" (gstring/format "$%.2f" (:amount payment) ) ") "
|
|
(when (= :cleared (:status (:payment payment)))
|
|
(str " - " (:post-date (:transaction (:payment payment))))))]))]]
|
|
[:span {:style {:margin-right "1em"}}]])
|
|
(when (and (get actions :edit)
|
|
(not= ":voided" (:status i)))
|
|
[buttons/fa-icon {:icon "fa-pencil"
|
|
:event [::form/editing 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]}])]]]))
|
|
|
|
(defn invoice-table [{:keys [id data checked status check-boxes on-check-changed overrides actions]}]
|
|
(let [selected-client @(re-frame/subscribe [::subs/client])
|
|
{:keys [sort]} @(re-frame/subscribe [::table-params])
|
|
selected-client @(re-frame/subscribe [::subs/client])
|
|
is-loading? (= :loading (:state status))
|
|
is-sorted-by-vendor? (and (= "vendor" (:sort-key (first sort)))
|
|
(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 {:on-params-change (fn [p]
|
|
(re-frame/dispatch [::params-changed p]))
|
|
:on-check-changed on-check-changed
|
|
:params @(re-frame/subscribe [::table-params])
|
|
:checked checked
|
|
:status status
|
|
:check-boxes? check-boxes
|
|
:column-count (if selected-client 8 9)}
|
|
[grid/controls data
|
|
[:div.level-item
|
|
"Outstanding " (nf (:outstanding data))]]
|
|
(for [invoices invoice-groups]
|
|
^{:key (or (:id (first invoices)) "init")}
|
|
[grid/table {:fullwidth true}
|
|
[grid/header {}
|
|
[grid/row {}
|
|
(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"}} "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 [client payments expense-accounts invoice-number date due total outstanding-balance id vendor] :as i} invoices]
|
|
^{:key id}
|
|
[row {:invoice i
|
|
:selected-client selected-client
|
|
:actions actions
|
|
:overrides overrides}])]])]))
|