(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}])]])]))