From fdc1d3e9e402e8bebf8f38562da85bf69b46f905 Mon Sep 17 00:00:00 2001 From: Bryce Covert Date: Mon, 27 Jul 2020 21:28:02 -0700 Subject: [PATCH] Tons of small fixes --- src/clj/auto_ap/datomic/bank_accounts.clj | 2 +- src/clj/auto_ap/datomic/checks.clj | 6 + src/clj/auto_ap/datomic/ledger.clj | 2 + src/clj/auto_ap/datomic/migrate.clj | 11 +- src/clj/auto_ap/datomic/vendors.clj | 6 +- src/clj/auto_ap/graphql.clj | 18 +- src/clj/auto_ap/graphql/checks.clj | 16 ++ src/clj/auto_ap/graphql/invoices.clj | 11 +- src/clj/auto_ap/graphql/vendors.clj | 12 +- src/clj/auto_ap/parse/csv.clj | 34 ++- src/clj/auto_ap/parse/templates.clj | 11 + src/clj/auto_ap/parse/util.clj | 2 +- src/cljc/auto_ap/entities/invoice.cljc | 2 + src/cljs/auto_ap/events.cljs | 3 + .../views/components/invoices/side_bar.cljs | 8 +- .../views/components/vendor_dialog.cljs | 61 +++-- .../auto_ap/views/pages/admin/clients.cljs | 1 + src/cljs/auto_ap/views/pages/home.cljs | 58 ++++- .../auto_ap/views/pages/import_invoices.cljs | 23 +- .../auto_ap/views/pages/invoices/common.cljs | 1 + .../auto_ap/views/pages/invoices/form.cljs | 210 ++++++++++-------- .../views/pages/ledger/profit_and_loss.cljs | 4 +- src/cljs/auto_ap/views/pages/payments.cljs | 6 +- .../views/pages/payments/side_bar.cljs | 39 +++- .../auto_ap/views/pages/payments/table.cljs | 40 +++- .../auto_ap/views/pages/unpaid_invoices.cljs | 19 +- 26 files changed, 428 insertions(+), 178 deletions(-) diff --git a/src/clj/auto_ap/datomic/bank_accounts.clj b/src/clj/auto_ap/datomic/bank_accounts.clj index 596bd001..a685a745 100644 --- a/src/clj/auto_ap/datomic/bank_accounts.clj +++ b/src/clj/auto_ap/datomic/bank_accounts.clj @@ -11,7 +11,7 @@ (update-in [:query :where] conj where))] (reduce #(update-in %1 [:query :where] conj %2) query rest))) -(def default-read '(pull ?e [*])) +(def default-read '(pull ?e [* {:client/_bank-accounts [:db/id]}])) (defn <-datomic [x] (->> x diff --git a/src/clj/auto_ap/datomic/checks.clj b/src/clj/auto_ap/datomic/checks.clj index 6575c2d8..21f6c781 100644 --- a/src/clj/auto_ap/datomic/checks.clj +++ b/src/clj/auto_ap/datomic/checks.clj @@ -72,6 +72,12 @@ :where ['[?e :payment/check-number ?check-number]]} :args [(:check-number args)]}) + (not-empty (:invoice-number args)) + (merge-query {:query {:in ['?invoice-number] + :where ['[?e :payment/invoices ?i] + '[?i :invoice/invoice-number ?invoice-number]]} + :args [(:invoice-number args)]}) + (:bank-account-id args) (merge-query {:query {:in ['?bank-account-id] :where ['[?e :payment/bank-account ?bank-account-id]]} diff --git a/src/clj/auto_ap/datomic/ledger.clj b/src/clj/auto_ap/datomic/ledger.clj index 00c9a5f3..b6f4816f 100644 --- a/src/clj/auto_ap/datomic/ledger.clj +++ b/src/clj/auto_ap/datomic/ledger.clj @@ -115,11 +115,13 @@ (group-by :db/id))] (->> ids (map results) + (filter identity) (map first)))) (defn get-graphql [args] (let [db (d/db (d/connect uri)) {ids-to-retrieve :ids matching-count :count} (raw-graphql-ids db args)] + [(->> (graphql-results ids-to-retrieve db args)) matching-count])) diff --git a/src/clj/auto_ap/datomic/migrate.clj b/src/clj/auto_ap/datomic/migrate.clj index 3a7d3502..e697439e 100644 --- a/src/clj/auto_ap/datomic/migrate.clj +++ b/src/clj/auto_ap/datomic/migrate.clj @@ -299,7 +299,16 @@ {:db/ident :client/week-b-credits :db/doc "How much money gets credited each week" :db/valueType :db.type/double - :db/cardinality :db.cardinality/one}]]}} + :db/cardinality :db.cardinality/one}]]} + :auto-ap/auto-pay {:txes [[{:db/ident :vendor/automatically-paid-when-due + :db/doc "The clients for which invoices will automatically be paid." + :db/valueType :db.type/ref + :db/cardinality :db.cardinality/many} + + {:db/ident :invoice/automatically-paid-when-due + :db/doc "Whether this invoice should be marked as paid when it's due" + :db/valueType :db.type/boolean + :db/cardinality :db.cardinality/one}]]}} ] (println "Conforming database...") (c/ensure-conforms conn norms-map) diff --git a/src/clj/auto_ap/datomic/vendors.clj b/src/clj/auto_ap/datomic/vendors.clj index f3ffabf2..1b0f8f78 100644 --- a/src/clj/auto_ap/datomic/vendors.clj +++ b/src/clj/auto_ap/datomic/vendors.clj @@ -15,7 +15,8 @@ (def default-read '[* {:vendor/account-overrides [* {:vendor-account-override/client [:client/name :db/id] :vendor-account-override/account [:account/name :account/numeric-code :db/id]}] - :vendor/terms-overrides [* {:vendor-terms-override/client [:client/name :db/id]}]}]) + :vendor/terms-overrides [* {:vendor-terms-override/client [:client/name :client/code :db/id]}] + :vendor/automatically-paid-when-due [:db/id :client/name]}]) (defn get-graphql [args] (->> (cond-> {:query {:find [(list 'pull '?e default-read)] @@ -32,7 +33,8 @@ {:default-account [:account/name :db/id :account/location] :vendor/account-overrides [* {:vendor-account-override/client [:client/name :db/id] :vendor-account-override/account [:account/name :account/numeric-code :db/id]}] - :vendor/terms-overrides [* {:vendor-terms-override/client [:client/name :db/id]}]}]) + :vendor/terms-overrides [* {:vendor-terms-override/client [:client/name :db/id]}] + :vendor/automatically-paid-when-due [:db/id :client/name]}]) :in $ ?e :where [?e]] (d/db (d/connect uri)) diff --git a/src/clj/auto_ap/graphql.clj b/src/clj/auto_ap/graphql.clj index 5e0ab040..28112ea1 100644 --- a/src/clj/auto_ap/graphql.clj +++ b/src/clj/auto_ap/graphql.clj @@ -145,6 +145,7 @@ :code {:type 'String} :terms {:type 'Int} :hidden {:type 'Boolean} + :automatically_paid_when_due {:type '(list :client)} :terms_overrides {:type '(list :terms_override)} :account_overrides {:type '(list :vendor_account_override)} @@ -305,7 +306,8 @@ :client_id {:type 'Int} :payments {:type '(list :invoice_payment)} :vendor {:type :vendor} - :client {:type :client}}} + :client {:type :client} + :automatically_paid_when_due {:type 'Boolean}}} @@ -496,6 +498,7 @@ :amount_lte {:type :money} :amount_gte {:type :money} :check_number_like {:type 'String} + :invoice_number {:type 'String} :start {:type 'Int} :sort {:type '(list :sort_item)}} @@ -608,6 +611,7 @@ :terms {:type 'Int} :terms_overrides {:type '(list :add_terms_override)} :code {:type 'String} + :automatically_paid_when_due {:type '(list :id)} :hidden {:type 'Boolean} :print_as {:type 'String} @@ -630,6 +634,7 @@ :invoice_number {:type 'String} :expense_accounts {:type '(list :edit_expense_account)} :location {:type :iso_date} + :automatically_paid_when_due {:type 'Boolean} :date {:type :iso_date} :due {:type :iso_date} :client_id {:type :id} @@ -642,6 +647,7 @@ :invoice_number {:type 'String} :expense_accounts {:type '(list :edit_expense_account)} :date {:type :iso_date} + :automatically_paid_when_due {:type 'Boolean} :due {:type :iso_date} :total {:type 'Float}}} :edit_transaction @@ -971,7 +977,7 @@ '[?i :invoice/due ?due] '[(<= ?due ?due-before)] '[?i :invoice/outstanding-balance ?outstanding]]} - :args [(d/db (d/connect uri)) client_id (coerce/to-date (t/plus (time/local-now) (t/days 31)))]}) + :args [(d/db (d/connect uri)) client_id (coerce/to-date (t/plus (time/local-now) (t/days 180)))]}) outstanding-checks (reduce + 0.0 @@ -983,7 +989,7 @@ '(or [?p :payment/type :payment-type/debit] [?p :payment/type :payment-type/check])]} - :args [(d/db (d/connect uri)) client_id (coerce/to-date (t/plus (time/local-now) (t/days 31)))]}))) + :args [(d/db (d/connect uri)) client_id (coerce/to-date (t/plus (time/local-now) (t/days 180)))]}))) recent-fulfillments (d/query {:query {:find '[?f ?d] :in '[$ ?client ?min-date] :where ['[?t :transaction/forecast-match ?f] @@ -993,7 +999,7 @@ :args [(d/db (d/connect uri)) client_id (coerce/to-date (t/plus (time/local-now) (t/months -2)))]}) forecasted-transactions (for [{:forecasted-transaction/keys [amount identifier day-of-month] :db/keys [id]} forecasted-transactions - month (range -1 2) + month (range -1 7) :let [next (t/plus (t/local-date (t/year (time/local-now)) (t/month (time/local-now)) day-of-month ) @@ -1018,7 +1024,7 @@ (or week-a-credits 0) (or week-b-credits 0)) :date (coerce/to-date-time date)}) - (take 5 (time/day-of-week-seq 1))) + (take (* 7 4) (time/day-of-week-seq 1))) (filter #(>= (:amount %) 0) forecasted-transactions)) :upcoming_debits (into (mapv (fn [date] @@ -1026,7 +1032,7 @@ (or week-a-debits 0) (or week-b-debits 0))) :date (coerce/to-date-time date)}) - (take 5 (time/day-of-week-seq 1))) + (take (* 7 4) (time/day-of-week-seq 1))) (filter #(< (:amount %) 0) forecasted-transactions)) }))) diff --git a/src/clj/auto_ap/graphql/checks.clj b/src/clj/auto_ap/graphql/checks.clj index ebb9975f..25d8eb7e 100644 --- a/src/clj/auto_ap/graphql/checks.clj +++ b/src/clj/auto_ap/graphql/checks.clj @@ -293,6 +293,19 @@ (conj payment) (into (invoice-payments invoices invoice-amounts))))) +(defn validate-belonging [client-id invoices bank-account] + (when-not (apply = client-id (map (comp :db/id :invoice/client) invoices )) + (throw (ex-info "You can't pay for that invoice from this bank account." + {:validation-error "You can't pay for that invoice from this bank account." + :client-id client-id + :invoices (map :invoice/invoice-number invoices)})) + ) + (when-not (= client-id (:db/id (:client/_bank-accounts bank-account))) + (throw (ex-info "The selected bank doesn't belong to this client" + {:validation-error "The selected bank doesn't belong to this client" + :client-id client-id + :invoices (map :invoice/invoice-number invoices)})))) + (defn print-checks [invoice-payments client-id bank-account-id type] (let [type (keyword "payment-type" (name type)) invoices (d-invoices/get-multi (map :invoice-id invoice-payments)) @@ -303,6 +316,8 @@ invoices-grouped-by-vendor (group-by (comp :db/id :invoice/vendor) invoices) bank-account (d-bank-accounts/get-by-id bank-account-id) + _ (validate-belonging client-id invoices bank-account) + checks (->> (for [[[vendor-id invoices] index] (map vector invoices-grouped-by-vendor (range))] (invoices->entities invoices (vendors vendor-id) client bank-account type index invoice-amounts)) (reduce into []) @@ -347,6 +362,7 @@ bank-account (d-bank-accounts/get-by-id bank-account-id) _ (doseq [invoice invoices] (assert-can-see-client (:id context) (:invoice/client invoice))) + _ (validate-belonging (:db/id (:client/_bank-accounts bank-account)) invoices bank-account) invoice-payment-lookup (by :invoice_id :amount (:invoice_payments args)) base-payment (base-payment invoices (:invoice/vendor (first invoices)) diff --git a/src/clj/auto_ap/graphql/invoices.clj b/src/clj/auto_ap/graphql/invoices.clj index be468765..0bc73b95 100644 --- a/src/clj/auto_ap/graphql/invoices.clj +++ b/src/clj/auto_ap/graphql/invoices.clj @@ -57,7 +57,7 @@ :account account_id :location location})) -(defn add-invoice-transaction [{:keys [total invoice_number location client_id vendor_id vendor_name date due expense_accounts] :as in}] +(defn add-invoice-transaction [{:keys [total invoice_number location automatically_paid_when_due client_id vendor_id vendor_name date due expense_accounts] :as in}] (println date) (let [vendor (d-vendors/get-by-id vendor_id) account (:vendor/default-account vendor) @@ -77,7 +77,8 @@ expense_accounts)} (:vendor/terms vendor) (assoc :invoice/due (coerce/to-date (time/plus date (time/days (d-vendors/terms-for-client-id vendor client_id))))) - due (assoc :invoice/due (coerce/to-date due))))) + due (assoc :invoice/due (coerce/to-date due)) + (boolean? automatically_paid_when_due) (assoc :invoice/automatically-paid-when-due automatically_paid_when_due)))) (defn deleted-expense-accounts [invoice expense-accounts] @@ -116,7 +117,7 @@ ->graphql))) -(defn edit-invoice [context {{:keys [id due invoice_number total vendor_id date client_id expense_accounts] :as in} :invoice} value] +(defn edit-invoice [context {{:keys [id due invoice_number total vendor_id date client_id expense_accounts automatically_paid_when_due] :as in} :invoice} value] (let [invoice (d-invoices/get-by-id id) _ (when (seq (doto (d-invoices/find-conflicting {:db/id id :invoice/invoice-number invoice_number @@ -136,11 +137,13 @@ updated-invoice (cond-> {:db/id id :invoice/invoice-number invoice_number :invoice/date (coerce/to-date date) + :invoice/total total :invoice/outstanding-balance (- total paid-amount) :invoice/expense-accounts (map expense-account->entity expense_accounts)} - due (assoc :invoice/due (coerce/to-date due)))] + due (assoc :invoice/due (coerce/to-date due)) + (boolean? automatically_paid_when_due) (assoc :invoice/automatically-paid-when-due automatically_paid_when_due))] @(d/transact (d/connect uri) (concat [updated-invoice] (map (fn [d] [:db/retract id :invoice/expense-accounts d]) deleted))) (-> (d-invoices/get-by-id id) diff --git a/src/clj/auto_ap/graphql/vendors.clj b/src/clj/auto_ap/graphql/vendors.clj index 63da3f8f..e8a30c6b 100644 --- a/src/clj/auto_ap/graphql/vendors.clj +++ b/src/clj/auto_ap/graphql/vendors.clj @@ -70,11 +70,21 @@ :email (:email secondary_contact)}) )})] (is-admin? (:id context)) (conj [:reset (if id id "vendor") :vendor/account-overrides account-overrides]) - (is-admin? (:id context)) (conj [:reset (if id id "vendor") :vendor/terms-overrides terms-overrides])) + (is-admin? (:id context)) (conj [:reset (if id id "vendor") :vendor/terms-overrides terms-overrides]) + (is-admin? (:id context)) (conj [:reset (if id id "vendor") :vendor/automatically-paid-when-due + (doto (mapv + (fn [apwd] + {:db/id apwd}) + (:automatically_paid_when_due in)) + println)])) + + _ (println transaction) transaction-result @(d/transact (d/connect uri) transaction)] (-> (d-vendors/get-by-id (or (-> transaction-result :tempids (get "vendor")) id)) + + (doto println) (->graphql)))) (defn merge-vendors [context {:keys [from to]} value] diff --git a/src/clj/auto_ap/parse/csv.clj b/src/clj/auto_ap/parse/csv.clj index 2d713912..7771d0c2 100644 --- a/src/clj/auto_ap/parse/csv.clj +++ b/src/clj/auto_ap/parse/csv.clj @@ -70,24 +70,22 @@ [rows] (println "Importing Sysco-styled 1") (let [header (first rows)] - (doto - (transduce - (comp (drop 1) - (map (fn [row] - (into {} (map vector header row)))) - (map (fn [row] - {:vendor-code nil - :customer-identifier (str (get row "Ship-To Name") " " (or (get row "Ship-To Number") - (get row "\"Ship-To Number\""))) - :invoice-number (str/trim (get row "Invoice Number")) - :date (parse-date-fallover (get row "Invoice Date") ["yyyy-MM-dd"]) - :total (str/replace (get row "Original Amount") #"[,\$]" "") - :text (str/join " " (vals row)) - :full-text (str/join " " (vals row))}))) - conj - [] - rows) - println))) + (transduce + (comp (drop 1) + (map (fn [row] + (into {} (map vector header row)))) + (map (fn [row] + {:vendor-code nil + :customer-identifier (str (get row "Ship-To Name") " " (or (get row "Ship-To Number") + (get row "\"Ship-To Number\""))) + :invoice-number (str/trim (get row "Invoice Number")) + :date (parse-date-fallover (get row "Invoice Date") ["yyyy-MM-dd" "M/d/yyyy"]) + :total (str/replace (get row "Original Amount") #"[,\$]" "") + :text (str/join " " (vals row)) + :full-text (str/join " " (vals row))}))) + conj + [] + rows))) (defmethod parse-csv :mama-lus [rows] diff --git a/src/clj/auto_ap/parse/templates.clj b/src/clj/auto_ap/parse/templates.clj index 51b67cd9..32b70bbf 100644 --- a/src/clj/auto_ap/parse/templates.clj +++ b/src/clj/auto_ap/parse/templates.clj @@ -175,6 +175,17 @@ :multi #"\n" :multi-match? #"^\s+.*?\d{6,}.*?\$"} + + ;; C & L + {:vendor "C&L Produce" + :keywords [#"440 Franklin Street"] + :extract {:date #"([0-9]+/[0-9]+/[0-9]+)" + :customer-identifier #"Bill To.*\n.*\n\s+(.*?)\s{2,}" + :invoice-number #"Invoice #.*\n.*\n.*?(\d{5,})\n" + :total #"Total\s+\$([0-9\.,]+)"} + :parser {:date [:clj-time "MM/dd/yy"] + :total [:trim-commas nil]}} + {:vendor "General Produce Company" :keywords [#"generalproduce.com"] :extract {:date #"INVOICE DATE.*\n.*?([0-9]+/[0-9]+/[0-9]+)" diff --git a/src/clj/auto_ap/parse/util.clj b/src/clj/auto_ap/parse/util.clj index 1708a09e..154bd86d 100644 --- a/src/clj/auto_ap/parse/util.clj +++ b/src/clj/auto_ap/parse/util.clj @@ -39,7 +39,7 @@ (reduced (time/from-time-zone (f/parse (f/formatter format) value) (time/time-zone-for-id "America/Los_Angeles"))) (catch Exception e - (println e) + (println (.getMessage e)) nil))) nil format) diff --git a/src/cljc/auto_ap/entities/invoice.cljc b/src/cljc/auto_ap/entities/invoice.cljc index d3436209..84f946c9 100644 --- a/src/cljc/auto_ap/entities/invoice.cljc +++ b/src/cljc/auto_ap/entities/invoice.cljc @@ -10,6 +10,7 @@ (s/def ::due (s/nilable ::shared/date)) (s/def ::total ::shared/money) (s/def ::vendor-id ::shared/identifier) +(s/def ::automatically-paid-when-due (s/nilable boolean?)) (s/def ::invoice (s/keys :req-un [::client ::invoice-number @@ -18,4 +19,5 @@ ::total] :opt-un [::vendor-name ::due + ::automatically-paid-when-due ])) diff --git a/src/cljs/auto_ap/events.cljs b/src/cljs/auto_ap/events.cljs index a4595464..ac68d5d2 100644 --- a/src/cljs/auto_ap/events.cljs +++ b/src/cljs/auto_ap/events.cljs @@ -50,12 +50,15 @@ [:vendor [:id :name :hidden [:default-account [:name :id :location]] [:primary-contact [:name :phone :email :id]] [:secondary-contact [:id :name :phone :email]] :print-as :invoice-reminder-schedule :code [:account-overrides [[:client [:id :name]] :id [:account [:id :numeric-code :name]]]] + + [:automatically-paid-when-due [:id :name]] [:terms-overrides [[:client [:id :name]] :id :terms]]]] [:accounts [:numeric-code :location :name :type :account_set :applicability :id [:client-overrides [:name [:client [:name :id]]]]]]]} :on-success [::received-initial]}})))) (def vendor-query [:id :name :hidden :terms [:default-account [:name :id :location]] [:account-overrides [[:client [:id :name]] :id [:account [:id :numeric-code :name]]]] + [:automatically-paid-when-due [:id :name]] [:terms-overrides [[:client [:id :name]] :id :terms]] [:primary-contact [:name :phone :email :id]] [:secondary-contact [:id :name :phone :email]] diff --git a/src/cljs/auto_ap/views/components/invoices/side_bar.cljs b/src/cljs/auto_ap/views/components/invoices/side_bar.cljs index 4d3334ec..eedaa5b5 100644 --- a/src/cljs/auto_ap/views/components/invoices/side_bar.cljs +++ b/src/cljs/auto_ap/views/components/invoices/side_bar.cljs @@ -25,6 +25,7 @@ :<- [::subs/vendors-by-id] :<- [::subs/query-params] (fn [[specific-filters vendors-by-id query-params] ] + (println "Q:UERY" query-params) (let [url-filters (-> query-params (select-keys #{:vendor-id :date-range @@ -42,8 +43,8 @@ :amount-lte (:amount-lte url-filters)} :settled {:amount-gte (:amount-gte url-filters) :amount-lte (:amount-lte url-filters)}} - :invoice-number-like {:raw (:invoice-number-like url-filters) - :settled (:invoice-number-like url-filters)}}] + :invoice-number-like {:raw (str (:invoice-number-like url-filters)) + :settled (str (:invoice-number-like url-filters))}}] (merge url-filters specific-filters )))) (re-frame/reg-sub @@ -56,7 +57,8 @@ ::filter-params :<- [::filters] :<- [::subs/active-page] - (fn [[filters ap]] + + (fn [[filters ap ]] {:vendor-id (:id (:vendor filters)) :date-range (:date-range filters) :due-range (:due-range filters) diff --git a/src/cljs/auto_ap/views/components/vendor_dialog.cljs b/src/cljs/auto_ap/views/components/vendor_dialog.cljs index d340dd82..70182371 100644 --- a/src/cljs/auto_ap/views/components/vendor_dialog.cljs +++ b/src/cljs/auto_ap/views/components/vendor_dialog.cljs @@ -106,8 +106,7 @@ (re-frame/reg-event-fx ::save [with-user with-is-admin? (forms/triggers-loading ::vendor-form) (forms/in-form ::vendor-form)] - (fn [{:keys [user is-admin?] {{:keys [name hidden print-as terms invoice-reminder-schedule primary-contact secondary-contact address default-account terms-overrides account-overrides id] :as data} :data} :db} _] - (println user is-admin?) + (fn [{:keys [user is-admin?] {{:keys [name hidden print-as terms invoice-reminder-schedule primary-contact automatically-paid-when-due secondary-contact address default-account terms-overrides account-overrides id] :as data} :data} :db} _] (when (s/valid? ::entity/vendor data) { :graphql {:token user @@ -118,28 +117,55 @@ :name name :print-as print-as :terms terms - :terms-overrides (mapv - (fn [{:keys [client override id]}] - {:id id - :client-id (:id client) - :terms override}) - terms-overrides) :default-account-id (:id default-account) - :account-overrides (mapv - (fn [{:keys [client override id]}] - {:id id - :client-id (:id client) - :account-id (:id override)}) - account-overrides) :address address :primary-contact primary-contact :secondary-contact secondary-contact :invoice-reminder-schedule invoice-reminder-schedule} - is-admin? (assoc :hidden hidden))} + is-admin? (assoc :hidden hidden + :terms-overrides (mapv + (fn [{:keys [client override id]}] + {:id id + :client-id (:id client) + :terms override}) + terms-overrides) + :account-overrides (mapv + (fn [{:keys [client override id]}] + {:id id + :client-id (:id client) + :account-id (:id override)}) + account-overrides) + :automatically-paid-when-due (mapv + :id + automatically-paid-when-due)))} events/vendor-query]}]} :on-success [::save-complete] :on-error [::forms/save-error ::vendor-form]}}))) +(defn client-list [{:keys [override-key override-value-key change-event default-key data]} template] + (let [clients @(re-frame/subscribe [::subs/clients]) + is-admin? @(re-frame/subscribe [::subs/is-admin?])] + (when is-admin? + [horizontal-field + [:label.label "Client"] + (doall + (for [[i override] (map vector (range) (conj (override-key data) {:key (random-uuid)}))] + ^{:key (or + (:id override) + (:key override))} + [:div.columns + [:div.column + [bind-field + [typeahead-entity {:matches clients + :match->text :name + :type "typeahead-entity" + :field [override-key i] + :event change-event + :subscription data}]]] + + [:div.column.is-1 + [:a.button {:on-click (dispatch-event [::removed-override override-key i])} [:span.icon [:span.icon-remove]]]]]))]))) + (defn default-with-overrides [{:keys [override-key override-value-key change-event default-key data]} template] (let [clients @(re-frame/subscribe [::subs/clients]) is-admin? @(re-frame/subscribe [::subs/is-admin?])] @@ -224,6 +250,11 @@ :spec ::entity/terms :event change-event :subscription data}])] + + [:h2.subtitle "Automatically mark as paid when due"] + [client-list {:data data :change-event change-event + :override-key :automatically-paid-when-due}] + [:h2.subtitle "Expense Accounts"] [default-with-overrides {:data data :change-event change-event diff --git a/src/cljs/auto_ap/views/pages/admin/clients.cljs b/src/cljs/auto_ap/views/pages/admin/clients.cljs index 4e1e9243..bd74be11 100644 --- a/src/cljs/auto_ap/views/pages/admin/clients.cljs +++ b/src/cljs/auto_ap/views/pages/admin/clients.cljs @@ -40,6 +40,7 @@ ::can-submit :<- [::forms/form ::form] (fn [{:keys [data status]} _] + (println (s/explain ::entity/client data)) (s/valid? ::entity/client data))) diff --git a/src/cljs/auto_ap/views/pages/home.cljs b/src/cljs/auto_ap/views/pages/home.cljs index 2137cae3..df7747ca 100644 --- a/src/cljs/auto_ap/views/pages/home.cljs +++ b/src/cljs/auto_ap/views/pages/home.cljs @@ -167,9 +167,26 @@ :debits (upcoming-debits (date->str start-date) 0.0) :outstanding-payments (- outstanding-payments) :query-params (cemerick.url/map->query {:due-range {:end (date->str start-date standard)}})}) - (if (= :seven-days (:cash-flow-range chart-options)) + (condp = (:cash-flow-range chart-options) + :seven-days (range 1 7) - (range 1 31))))))) + :thirty-days + (range 1 31) + + :sixty-days + (range 1 61) + + :ninety-days + (range 1 91) + + :one-hundred-twenty-days + (range 1 121) + + :one-hundred-fifty-days + (range 1 151) + + :one-hundred-eighty-days + (range 1 181))))))) (re-frame/reg-event-fx ::mounted @@ -191,6 +208,12 @@ [:upcoming-debits [:date :amount]]]]]} :on-success [::received]}})) +(defn cash-flow-range-button [{:keys [name value chart-options]}] + [:a.button {:class (when (= value (:cash-flow-range chart-options)) + ["is-info" "is-selected"]) + :on-click (dispatch-event [::select-cash-flow-range value])} + name]) + (defn home-content [] (let [client-id (-> @(re-frame/subscribe [::subs/client]) :id) chart-options @(re-frame/subscribe [::chart-options])] @@ -207,14 +230,29 @@ [:h1.title.is-4 "Cash Flow"] [:div.buttons.has-addons - [:a.button {:class (when (= :seven-days (:cash-flow-range chart-options)) - ["is-info" "is-selected"]) - :on-click (dispatch-event [::select-cash-flow-range :seven-days])} - "7 days"] - [:a.button {:class (when (= :thirty-days (:cash-flow-range chart-options)) - ["is-info" "is-selected"]) - :on-click (dispatch-event [::select-cash-flow-range :thirty-days])} - "30 days"]] + [cash-flow-range-button {:name "7 days" + :value :seven-days + :chart-options chart-options}] + [cash-flow-range-button {:name "30 days" + :value :thirty-days + :chart-options chart-options}] + + [cash-flow-range-button {:name "60 days" + :value :sixty-days + :chart-options chart-options}] + + [cash-flow-range-button {:name "90 days" + :value :ninety-days + :chart-options chart-options}] + [cash-flow-range-button {:name "120 days" + :value :one-hundred-twenty-days + :chart-options chart-options}] + [cash-flow-range-button {:name "150 days" + :value :one-hundred-fifty-days + :chart-options chart-options}] + [cash-flow-range-button {:name "180 days" + :value :one-hundred-eighty-days + :chart-options chart-options}]] (make-cash-flow-chart {:width 800 :height 500 diff --git a/src/cljs/auto_ap/views/pages/import_invoices.cljs b/src/cljs/auto_ap/views/pages/import_invoices.cljs index 8372563e..cc363a23 100644 --- a/src/cljs/auto_ap/views/pages/import_invoices.cljs +++ b/src/cljs/auto_ap/views/pages/import_invoices.cljs @@ -22,6 +22,7 @@ (reagent/create-class {:display-name "dropzone" :component-did-mount (fn [this] + (println "HERE?") (js/Dropzone. (rdom/dom-node this) (clj->js {:init (fn [] (.on (js-this) "success" (fn [_ files] @@ -36,6 +37,7 @@ :previewsContainer "#dz-hidden" :previewTemplate "
"}))) :reagent-render (fn [] + {:key batch} [:form.dz {:action "/api/invoices/upload"} [:div.field.has-addons [:p.control @@ -78,11 +80,24 @@ (fn [db] (-> db (::params {})))) +(re-frame/reg-sub + ::batch + (fn [db] + (-> db (::batch 0)))) + (re-frame/reg-event-fx ::invalidated (fn [cofx [_ params]] {:dispatch [::params-change @(re-frame/subscribe [::params])]})) +(re-frame/reg-event-fx + ::completed + (fn [cofx [_ params]] + (println (::batch (:db cofx))) + {:dispatch [::invalidated] + :db (-> (:db cofx) + (update ::batch inc))})) + (re-frame/reg-event-fx ::params-change (fn [cofx [_ params]] @@ -121,7 +136,7 @@ :venia/queries [[:reject-invoices {:invoices (keys invoices)} []]]} - :on-success [::invalidated]} + :on-success [::completed]} })) (re-frame/reg-event-fx @@ -135,7 +150,7 @@ :venia/queries [[:approve-invoices {:invoices (keys invoices)} []]]} - :on-success [::invalidated]} + :on-success [::completed]} })) (re-frame/reg-event-fx @@ -194,11 +209,13 @@ (let [invoice-page (re-frame/subscribe [::invoice-page]) status (re-frame/subscribe [::subs/status]) error (re-frame/subscribe [::error]) + batch (re-frame/subscribe [::batch]) client (:id @(re-frame/subscribe [::subs/client]))] - ^{:key client} + ^{:key (str client "-" @batch)} [:div [:h1.title "Upload invoices"] + ^{:key (str @batch)} [dropzone] [:div {:class "section"} diff --git a/src/cljs/auto_ap/views/pages/invoices/common.cljs b/src/cljs/auto_ap/views/pages/invoices/common.cljs index fd22f017..d670a2c6 100644 --- a/src/cljs/auto_ap/views/pages/invoices/common.cljs +++ b/src/cljs/auto_ap/views/pages/invoices/common.cljs @@ -1,6 +1,7 @@ (ns auto-ap.views.pages.invoices.common) (def invoice-read [:id :total :outstanding-balance :date :due :invoice-number :status + :automatically-paid-when-due [:client [:id :name :locations]] [:payments [:amount [:payment [:amount :s3_url :check_number ]]]] [:vendor [:id :name]] diff --git a/src/cljs/auto_ap/views/pages/invoices/form.cljs b/src/cljs/auto_ap/views/pages/invoices/form.cljs index 791996bb..a620d556 100644 --- a/src/cljs/auto_ap/views/pages/invoices/form.cljs +++ b/src/cljs/auto_ap/views/pages/invoices/form.cljs @@ -35,7 +35,7 @@ (re-frame/reg-sub ::create-query :<- [::forms/form ::form] - (fn [{:keys [data] {:keys [id invoice-number date due location total expense-accounts vendor client]} :data}] + (fn [{:keys [data] {:keys [id invoice-number date due location total expense-accounts automatically-paid-when-due vendor client]} :data}] {:venia/operation {:operation/type :mutation :operation/name "AddInvoice"} :venia/queries [{:query/data [:add-invoice @@ -43,6 +43,7 @@ :due due :vendor-id (:id vendor) :client-id (:id client) + :automatically-paid-when-due automatically-paid-when-due :invoice-number invoice-number :location location :total total @@ -58,13 +59,14 @@ (re-frame/reg-sub ::edit-query :<- [::forms/form ::form] - (fn [{:keys [data] {:keys [id invoice-number date due location total expense-accounts vendor client]} :data}] + (fn [{:keys [data] {:keys [id invoice-number date due location total expense-accounts vendor automatically-paid-when-due client]} :data}] {:venia/operation {:operation/type :mutation :operation/name "EditInvoice"} :venia/queries [{:query/data [:edit-invoice {:invoice {:id id :invoice-number invoice-number :date date + :automatically-paid-when-due automatically-paid-when-due :due due :total total :expense-accounts (map (fn [ea] @@ -79,13 +81,14 @@ (re-frame/reg-sub ::add-and-print-query (fn [db [_ bank-account-id type]] - (let [{:keys [data] {:keys [id invoice-number date location total expense-accounts vendor client]} :data} @(re-frame/subscribe [::forms/form ::form])] + (let [{:keys [data] {:keys [id invoice-number date location total expense-accounts automatically-paid-when-due vendor client]} :data} @(re-frame/subscribe [::forms/form ::form])] {:venia/operation {:operation/type :mutation :operation/name "AddAndPrintInvoice"} :venia/queries [{:query/data [:add-and-print-invoice {:invoice {:date date :vendor-id (:id vendor) :client-id (:id client) + :automatically-paid-when-due automatically-paid-when-due :invoice-number invoice-number :location location :total total @@ -127,6 +130,7 @@ :status (:status edit-invoice) :date (:date edit-invoice) :due (:due edit-invoice) + :automatically-paid-when-due (:automatically-paid-when-due edit-invoice) :invoice-number (:invoice-number edit-invoice) :total (:total edit-invoice) :original edit-invoice @@ -144,12 +148,17 @@ (fn [data field value] (let [locations @(re-frame/subscribe [::subs/locations-for-client (:id (:client data))])] (cond (and (= [:vendor] field) - value - (expense-accounts-field/can-replace-with-default? (:expense-accounts data))) - [[:expense-accounts] (expense-accounts-field/default-account (:expense-accounts data) - @(re-frame/subscribe [::subs/vendor-default-account value (:client data)]) - (:total data) - locations)] + value) + (cond-> [] + (expense-accounts-field/can-replace-with-default? (:expense-accounts data)) + (into [[:expense-accounts] (expense-accounts-field/default-account (:expense-accounts data) + @(re-frame/subscribe [::subs/vendor-default-account value (:client data)]) + (:total data) + locations)]) + + + true + (into [[:automatically-paid-when-due] (boolean ((set (map :id (:automatically-paid-when-due value))) (:id (:client data))))])) (= [:total] field) [[:expense-accounts] (recalculate-amounts (:expense-accounts data) value)] @@ -213,108 +222,117 @@ (defn form [{:keys [can-change-amount?] :as params}] [layouts/side-bar {:on-close (dispatch-event [::forms/form-closing ::form ])} (let [{:keys [data active? error id]} @(re-frame/subscribe [::forms/form ::form]) - {:keys [form field raw-field error-notification submit-button ]} invoice-form + {:keys [form-inline field raw-field error-notification submit-button ]} invoice-form exists? (:id data) can-change-amount? (#{:unpaid ":unpaid"} (:status data)) min-total (if (= (:total (:original data)) (:outstanding-balance (:original data))) nil (- (:total (:original data)) (:outstanding-balance (:original data))))] ^{:key id} - [form (assoc params :title "New Invoice") - (when-not @(re-frame/subscribe [::subs/client]) - [field "Client" - [typeahead-entity {:matches @(re-frame/subscribe [::subs/clients]) - :match->text :name - :type "typeahead" - :auto-focus (if @(re-frame/subscribe [::subs/client]) false true) - :field [:client] - :disabled exists? - :spec ::invoice/client}]]) + (form-inline (assoc params :title "New Invoice") + [:<> + (when-not @(re-frame/subscribe [::subs/client]) + (field "Client" + [typeahead-entity {:matches @(re-frame/subscribe [::subs/clients]) + :match->text :name + :type "typeahead" + :auto-focus (if @(re-frame/subscribe [::subs/client]) false true) + :field [:client] + :disabled exists? + :spec ::invoice/client}])) - [field "Vendor" - [typeahead-entity {:matches @(re-frame/subscribe [::subs/vendors]) - :match->text :name - :type "typeahead" - :disabled exists? - :auto-focus (if @(re-frame/subscribe [::subs/client]) true false) - :field [:vendor]}]] + (field "Vendor" + [typeahead-entity {:matches @(re-frame/subscribe [::subs/vendors]) + :match->text :name + :type "typeahead" + :disabled exists? + :auto-focus (if @(re-frame/subscribe [::subs/client]) true false) + :field [:vendor]}]) - [field "Date" - [date-picker {:class-name "input" - :class "input" - :format-week-number (fn [] "") - :previous-month-button-label "" - :placeholder "mm/dd/yyyy" - :next-month-button-label "" - :next-month-label "" - :type "date" - :field [:date] - :spec ::invoice/date}]] + (field "Date" + [date-picker {:class-name "input" + :class "input" + :format-week-number (fn [] "") + :previous-month-button-label "" + :placeholder "mm/dd/yyyy" + :next-month-button-label "" + :next-month-label "" + :type "date" + :field [:date] + :spec ::invoice/date}]) - [field "Due (optional)" - [date-picker {:class-name "input" - :class "input" - :format-week-number (fn [] "") - :previous-month-button-label "" - :placeholder "mm/dd/yyyy" - :next-month-button-label "" - :next-month-label "" - :type "date" - :field [:due] - :spec ::invoice/due}]] + (field "Due (optional)" + [date-picker {:class-name "input" + :class "input" + :format-week-number (fn [] "") + :previous-month-button-label "" + :placeholder "mm/dd/yyyy" + :next-month-button-label "" + :next-month-label "" + :type "date" + :field [:due] + :spec ::invoice/due}]) - [field "Invoice #" - [:input.input {:type "text" - :field [:invoice-number] - :spec ::invoice/invoice-number}]] + [:div.field + [:label.checkbox + (raw-field + [:input {:type "checkbox" + :field [:automatically-paid-when-due] + :spec ::invoice/automatically-paid-when-due}]) + " Mark as paid on due date"]] + + (field "Invoice #" + [:input.input {:type "text" + :field [:invoice-number] + :spec ::invoice/invoice-number}]) - [field "Total" - [money-field {:type "money" - :field [:total] - :disabled (if can-change-amount? "" "disabled") - :min min-total - :spec ::invoice/total - :step "0.01"}]] + (field "Total" + [money-field {:type "money" + :field [:total] + :disabled (if can-change-amount? "" "disabled") + :min min-total + :spec ::invoice/total + :step "0.01"}]) - [field nil - [expense-accounts-field {:type "expense-accounts" - :descriptor "expense account" - :locations (:locations (:client data)) - :max (:total data) - :client (or (:client data) @(re-frame/subscribe [::subs/client])) - :field [:expense-accounts]}]] + (field nil + [expense-accounts-field {:type "expense-accounts" + :descriptor "expense account" + :locations (:locations (:client data)) + :max (:total data) + :client (or (:client data) @(re-frame/subscribe [::subs/client])) + :field [:expense-accounts]}]) - + - [error-notification] + (error-notification) - [:div.columns - (when-not exists? - [:div.column - [drop-down {:header [:button.button.is-info.is-outlined.is-medium.is-fullwidth {:aria-haspopup true - :type "button" - :on-click (dispatch-event [::events/toggle-menu ::add-and-print-invoice ]) - :disabled (if @(re-frame/subscribe [::can-submit]) - "" - "disabled") + [:div.columns + (when-not exists? + [:div.column + [drop-down {:header [:button.button.is-info.is-outlined.is-medium.is-fullwidth {:aria-haspopup true + :type "button" + :on-click (dispatch-event [::events/toggle-menu ::add-and-print-invoice ]) + :disabled (if @(re-frame/subscribe [::can-submit]) + "" + "disabled") - :class (if false - "is-loading" - "")} - "Save & Pay " - [:span " "] - [:span.icon.is-small [:i.fa.fa-angle-down {:aria-hidden "true"}]]] - :class "is-fullwidth" - :id ::add-and-print-invoice} - [:div - (list - (for [{:keys [id number name type]} (->> (:bank-accounts (:client data)) (filter :visible) (sort-by :sort-order))] - (if (= :cash type) - ^{:key id} [:a.dropdown-item {:on-click (dispatch-event [::add-and-print params id :cash])} "With cash"] - (list - ^{:key (str id "-check")} [:a.dropdown-item {:on-click (dispatch-event [::add-and-print params id :check])} "Print checks from " name] - ^{:key (str id "-debit")} [:a.dropdown-item {:on-click (dispatch-event [::add-and-print params id :debit])} "Debit from " name]))))]]]) - [:div.column + :class (if false + "is-loading" + "")} + "Save & Pay " + [:span " "] + [:span.icon.is-small [:i.fa.fa-angle-down {:aria-hidden "true"}]]] + :class "is-fullwidth" + :id ::add-and-print-invoice} + [:div + (list + (for [{:keys [id number name type]} (->> (:bank-accounts (:client data)) (filter :visible) (sort-by :sort-order))] + (if (= :cash type) + ^{:key id} [:a.dropdown-item {:on-click (dispatch-event [::add-and-print params id :cash])} "With cash"] + (list + ^{:key (str id "-check")} [:a.dropdown-item {:on-click (dispatch-event [::add-and-print params id :check])} "Print checks from " name] + ^{:key (str id "-debit")} [:a.dropdown-item {:on-click (dispatch-event [::add-and-print params id :debit])} "Debit from " name]))))]]]) + [:div.column - [submit-button "Save"]]]])]) + (submit-button "Save")]]]))]) diff --git a/src/cljs/auto_ap/views/pages/ledger/profit_and_loss.cljs b/src/cljs/auto_ap/views/pages/ledger/profit_and_loss.cljs index e21d2f4d..04680067 100644 --- a/src/cljs/auto_ap/views/pages/ledger/profit_and_loss.cljs +++ b/src/cljs/auto_ap/views/pages/ledger/profit_and_loss.cljs @@ -337,13 +337,13 @@ [:td name] (map-periods (fn [i] - (let [amount (get-in all-accounts [(dec i) [numeric-code location] :amount] 0.0)] + (let [amount (get-in all-accounts [i [numeric-code location] :amount] 0.0)] [:<> [:td.has-text-right [:a {:on-click (dispatch-event [::investigate-clicked location numeric-code numeric-code i :current])} (->$ amount)]] [:td.has-text-right (->% (percent-of-sales amount all-accounts i location))]])) (fn [i] - [:td.has-text-right (->$ (- (get-in all-accounts [(dec i) [numeric-code location] :amount] 0.0) + [:td.has-text-right (->$ (- (get-in all-accounts [i [numeric-code location] :amount] 0.0) (get-in all-accounts [(dec i) [numeric-code location] :amount] 0.0)))]) periods)])] diff --git a/src/cljs/auto_ap/views/pages/payments.cljs b/src/cljs/auto_ap/views/pages/payments.cljs index c8b9d9ec..f1eb03bb 100644 --- a/src/cljs/auto_ap/views/pages/payments.cljs +++ b/src/cljs/auto_ap/views/pages/payments.cljs @@ -61,7 +61,8 @@ params [[:payments [:id :status :amount :type :check_number :s3_url [:bank-account [:name]] - :date [:vendor [:name :id]] [:client [:name :id]]]] + :date [:vendor [:name :id]] [:client [:name :id]] + [:invoices [:invoice-id [:invoice [:invoice-number]]]]]] :total :start :end]]]} @@ -79,7 +80,8 @@ :operation/name "VoidPayment"} :venia/queries [{:query/data [:void-payment {:payment-id (:id payment)} - [:id :status [:bank-account [:name]] :amount :check_number :s3_url :date [:vendor [:name :id]] [:client [:name :id]]]]}]} + [:id :status [:bank-account [:name]] :amount :check_number :s3_url :date [:vendor [:name :id]] [:client [:name :id]] + [:invoices [:invoice-id]]]]}]} :on-success [::payment-voided]}})) (re-frame/reg-event-db diff --git a/src/cljs/auto_ap/views/pages/payments/side_bar.cljs b/src/cljs/auto_ap/views/pages/payments/side_bar.cljs index fee613d2..f085afa7 100644 --- a/src/cljs/auto_ap/views/pages/payments/side_bar.cljs +++ b/src/cljs/auto_ap/views/pages/payments/side_bar.cljs @@ -26,7 +26,8 @@ :amount-gte :amount-lte :date-range - :check-number-like})) + :check-number-like + :invoice-number})) url-filters {:vendor (when-let [vendor-id (:vendor-id url-filters)] {:id (str vendor-id) :name (get-in vendors-by-id [(str vendor-id) :name] "Loading...")}) @@ -36,7 +37,9 @@ :settled {:amount-gte (:amount-gte url-filters) :amount-lte (:amount-lte url-filters)}} :check-number-like {:raw (:check-number-like url-filters) - :settled (:check-number-like url-filters)}}] + :settled (:check-number-like url-filters)} + :invoice-number {:raw (:invoice-number url-filters) + :settled (:invoice-number url-filters)}}] (println "URL filters" url-filters) (merge url-filters specific-filters )))) @@ -54,7 +57,8 @@ :date-range (:date-range filters) :amount-gte (:amount-gte (:settled (:amount-range filters))) :amount-lte (:amount-lte (:settled (:amount-range filters))) - :check-number-like (:settled (:check-number-like filters))})) + :check-number-like (:settled (:check-number-like filters)) + :invoice-number (:settled (:invoice-number filters))})) (re-frame/reg-event-fx @@ -73,6 +77,13 @@ {:db (assoc db :settled description) :dispatch [::filter-changed :check-number-like [:settled] description]})) +(re-frame/reg-event-fx + ::invoice-number-settled + [(re-frame/path [::filters :invoice-number])] + (fn [{:keys [db]} [_ description]] + {:db (assoc db :settled description) + :dispatch [::filter-changed :invoice-number [:settled] description]})) + (re-frame/reg-event-fx ::amount-range-settled [(re-frame/path [::filters :amount-range])] @@ -90,6 +101,16 @@ :key ::check-number-like} :db (assoc db :raw description)})) +(re-frame/reg-event-fx + ::invoice-number-changed + [(re-frame/path [::filters :invoice-number])] + (fn [{:keys [db]} [_ description]] + {:dispatch-debounce + {:event [::invoice-number-settled description] + :time 500 + :key ::invoice-number} + :db (assoc db :raw description)})) + (re-frame/reg-event-fx ::amount-range-changed [(re-frame/path [::filters :amount-range])] @@ -107,6 +128,13 @@ :on-change (fn [x] (re-frame/dispatch [::check-number-like-changed (.. x -target -value) ]))} ]]]) +(defn invoice-number-filter [] + [:div.field + [:div.control [:input.input {:placeholder "SJ-12345" + :value (:raw @(re-frame/subscribe [::filter :invoice-number])) + :on-change (fn [x] + (re-frame/dispatch [::invoice-number-changed (.. x -target -value) ]))} ]]]) + (defn side-bar [] (let [ap @(re-frame/subscribe [::subs/active-page]) user @(re-frame/subscribe [::subs/user])] @@ -134,4 +162,7 @@ :value (:raw @(re-frame/subscribe [::filter :amount-range]))}]] [:p.menu-label "Check #"] - [:div [check-number-filter]]]])) + [:div [check-number-filter]] + + [:p.menu-label "Invoice #"] + [:div [invoice-number-filter]]]])) diff --git a/src/cljs/auto_ap/views/pages/payments/table.cljs b/src/cljs/auto_ap/views/pages/payments/table.cljs index 9010b171..2d040b5b 100644 --- a/src/cljs/auto_ap/views/pages/payments/table.cljs +++ b/src/cljs/auto_ap/views/pages/payments/table.cljs @@ -1,9 +1,15 @@ (ns auto-ap.views.pages.payments.table (:require [auto-ap.subs :as subs] + [auto-ap.routes :as routes] + [cemerick.url :as url] + [bidi.bidi :as bidi] [auto-ap.views.components.paginator :refer [paginator]] [auto-ap.views.components.sorter :refer [sorted-column]] [auto-ap.views.components.sort-by-list :refer [sort-by-list]] [auto-ap.views.utils :refer [date->str dispatch-event nf]] + + [auto-ap.views.components.dropdown :refer [drop-down drop-down-contents]] + [auto-ap.events :as events] [goog.string :as gstring] [re-frame.core :as re-frame])) @@ -29,7 +35,7 @@ selected-client :selected-client void-event :void-event }] - (let [{:keys [client s3-url bank-account payments type check-number date amount id vendor status] :as check} check] + (let [{:keys [client s3-url bank-account payments type check-number date amount id vendor status invoices] :as check} check] [:tr {:class (:class check)} (when-not selected-client [:td (:name client)]) @@ -45,11 +51,31 @@ [:td (date->str date) ] [:td.has-text-right (nf amount )] [:td status] - [:td - (when (or (= :pending status) - (and (#{":cash" :cash ":debit" :debit} type) - (not= :voided status))) - [:button.button.is-warning.is-outlined {:on-click (dispatch-event (conj void-event check))} [:span [:span.icon [:i.fa.fa-minus-circle]]]])]])) + [:td.expandable + [:div.buttons + (when (seq invoices) + [drop-down {:id [::invoices id] + :header [:button.button.badge {:data-badge (str (clojure.core/count invoices)) + :aria-haspopup true + :tab-index "0" + :on-click (dispatch-event [::events/toggle-menu [::invoices id]]) + } "Invoices"]} + [:div {:style {:max-width "250px" + :text-overflow "ellipsis" + :white-space "nowrap" + :overflow "hidden"}} + (for [invoice invoices] + ^{:key (:invoice-number (:invoice invoice))} + [:a.dropdown-item {:href (str (bidi/path-for routes/routes :invoices ) + "?" + (url/map->query {:invoice-number-like (:invoice-number (:invoice invoice))})) + :target "_new"} (str " " (:invoice-number (:invoice invoice)))])]]) + [:span {:style {:margin-left "1em"}}] + + (when (or (= :pending status) + (and (#{":cash" :cash ":debit" :debit} type) + (not= :voided status))) + [:button.button.is-warning.is-outlined {:on-click (dispatch-event (conj void-event check))} [:span [:span.icon [:i.fa.fa-minus-circle]]]])]]])) (defn table [{:keys [id payment-page status void-event]}] (let [{:keys [sort]} @(re-frame/subscribe [::table-params]) @@ -117,7 +143,7 @@ :sort-name "Status" :sort sort} "Status"] - [:th {:style {:width "8em"}} "" ]]] + [:th {:style {:width "15em"}} "" ]]] [:tbody (if (:loading @status) [:tr diff --git a/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs b/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs index 11d7643b..fd392bc5 100644 --- a/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs +++ b/src/cljs/auto_ap/views/pages/unpaid_invoices.cljs @@ -186,6 +186,16 @@ (if (= which (:id i)) (assoc-in i f v) i)))))) + + +(re-frame/reg-event-db + ::action-failed + [(re-frame/path [::invoice-page])] + (fn [db [_ result]] + (-> db + (assoc :action-notification (first (map :message result))) + (assoc :print-checks-loading? false)))) + (re-frame/reg-event-db ::edit-handwritten-payment (fn [db [_ which f v]] @@ -224,7 +234,8 @@ bank-account-id type (:client db)) - :on-success [::checks-created]}})) + :on-success [::checks-created] + :on-error [::action-failed]}})) @@ -273,6 +284,7 @@ invoices))) (assoc-in [::invoice-page :checked] nil) (assoc-in [::invoice-page :print-checks-loading?] false) + (assoc-in [::invoice-page :action-notification] nil) (assoc-in [::advanced-print-checks :printing?] false) (assoc-in [::advanced-print-checks :shown?] false)) :dispatch [::checks-printed pdf-url]}))) @@ -614,10 +626,13 @@ ])))) (defn unpaid-invoices-content [{:keys [status] :as params}] - (let [{:keys [checked print-checks-shown? print-checks-loading? advanced-print-shown? vendor-filter]} @(re-frame/subscribe [::invoice-page]) + (let [{:keys [checked action-notification print-checks-shown? print-checks-loading? advanced-print-shown? vendor-filter]} @(re-frame/subscribe [::invoice-page]) current-client @(re-frame/subscribe [::subs/client])] [:div #_[:h1.title (str (str/capitalize (or status "all")) " invoices")] + (when action-notification + [:div.notification + action-notification]) (when (= status :unpaid) [pay-button {:print-checks-shown? print-checks-shown? :checked-invoices checked :print-checks-loading? print-checks-loading?}]) [table/invoice-table {:id :unpaid