Files
integreat/src/cljs/auto_ap/views/components/invoice_table.cljs
2020-12-28 09:41:39 -08:00

242 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 days-until]]
[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]
[auto-ap.views.pages.data-page :as data-page]))
(defn query [params]
{:venia/queries [[:invoice_page
{
:start (:start params 0)
:sort (:sort params)
:per-page (:per-page params)
:vendor-id (:id (:vendor 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)
:invoice-number-like (:invoice-number-like params)
:client-id (:id @(re-frame/subscribe [::subs/client]))
: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)}
[[:invoices [:id :total :outstanding-balance :invoice-number :date :due :status :client-identifier :scheduled-payment
[: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-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 selected-client overrides checkable? expense-event actions]}]
(let [{:keys [client payments expense-accounts invoice-number date due total outstanding-balance id vendor] :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? :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 {} (date->str date) ]
[grid/cell {}
(when due
(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"} (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 check-boxes overrides actions data-page checkable-fn]}]
(let [selected-client @(re-frame/subscribe [::subs/client])
{:keys [data status table-params]} @(re-frame/subscribe [::data-page/page data-page])
selected-client @(re-frame/subscribe [::subs/client])
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 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
:checkable? (if checkable-fn
(checkable-fn i)
true)
:actions actions
:overrides overrides}])]])]))