working on import page

This commit is contained in:
2024-04-17 22:13:28 -07:00
parent 52662ca965
commit 263e432a6b
7 changed files with 606 additions and 22 deletions

View File

@@ -911,23 +911,24 @@
(let [[c [l]] (get-square-client-and-location "NGFA")]))
(clojure.data.csv/write-csv *out*
(for [c (get-square-clients)
l (:client/square-locations c)
:when (:square-location/client-location l)
bad-row (try (->> @(search c l (coerce/to-date-time #inst "2024-04-01T00:00:00-07:00") (coerce/to-date-time #inst "2024-04-15T23:59:00-07:00"))
(filter #(not (should-import-order? %)))
(map #(first (deref (order->sales-order c l %))))
(filter (fn already-exists [o]
(when (:sales-order/external-id o)
(seq (dc/q '[:find ?i
:in $ ?ei
:where [?i :sales-order/external-id ?ei]]
(dc/db conn)
(:sales-order/external-id o)))))))
(catch Exception e
[]))]
[(:client/code c) (atime/unparse-local (clj-time.coerce/to-date-time (:sales-order/date bad-row)) atime/normal-date) (:sales-order/total bad-row) (:sales-order/tax bad-row) (:sales-order/tip bad-row) (:db/id bad-row)])
:separator \tab)
(for [c (get-square-clients)
l (:client/square-locations c)
:when (:square-location/client-location l)
bad-row (try (->> @(search c l (coerce/to-date-time #inst "2024-04-01T00:00:00-07:00") (coerce/to-date-time #inst "2024-04-15T23:59:00-07:00"))
(filter #(not (should-import-order? %)))
(map #(first (deref (order->sales-order c l %))))
(filter (fn already-exists [o]
(when (:sales-order/external-id o)
(seq (dc/q '[:find ?i
:in $ ?ei
:where [?i :sales-order/external-id ?ei]]
(dc/db conn)
(:sales-order/external-id o)))))))
(catch Exception e
[]))]
[(:client/code c) (atime/unparse-local (clj-time.coerce/to-date-time (:sales-order/date bad-row)) atime/normal-date) (:sales-order/total bad-row) (:sales-order/tax bad-row) (:sales-order/tip bad-row) (:db/id bad-row)])
:separator \tab)

View File

@@ -83,7 +83,7 @@
(defn main-aside-nav- [request]
(let [selected (cond
(#{::invoice-route/all-page ::invoice-route/unpaid-page ::invoice-route/voided-page ::invoice-route/paid-page ::oi-routes/new} (:matched-route request))
(#{::invoice-route/all-page ::invoice-route/unpaid-page ::invoice-route/voided-page ::invoice-route/paid-page ::oi-routes/new ::invoice-route/import-page} (:matched-route request))
"invoices"
(#{:pos-sales :pos-expected-deposits :pos-tenders :pos-refunds :pos-cash-drawer-shifts} (:matched-route request))
@@ -138,8 +138,10 @@
(when (can? (:identity request)
{:subject :invoice
:activity :import})
(menu-button- {:href (bidi/path-for client-routes/routes
:import-invoices)} "Import"))
(menu-button- {:href (bidi/path-for ssr-routes/only-routes
::invoice-route/import-page)
:active? (= ::invoice-route/import-page (:matched-route request))
:hx-boost "true"} "Import"))
(when (can? (:identity request)

View File

@@ -263,6 +263,8 @@
:identity (:identity request)
:request request}
(apply com/breadcrumbs {} (:breadcrumbs grid-spec))
(when (:above-grid grid-spec)
( (:above-grid grid-spec) request))
[:div {:x-data (hx/json {:selected [] :all_selected false})
"x-bind:hx-vals" "JSON.stringify({selected: $data.selected, 'all-selected': $data.all_selected})"
:x-init "$watch('selected', s=> $dispatch('selectedChanged', {selected: s, all_selected: all_selected}) );
@@ -313,6 +315,12 @@
[:=>
[:cat [:map-of :keyword :any]]
[:map-of :keyword :any]]]
[:above-grid
{:optional true
:default (fn [request])}
[:=>
[:cat request-spec]
vector?]]
[:oob-render
{:optional true
:default (fn [request])}

View File

@@ -0,0 +1,569 @@
(ns auto-ap.ssr.invoice.import
(:require [auto-ap.client-routes :as client-routes]
[auto-ap.datomic
:refer [add-sorter-fields apply-pagination apply-sort-3 conn
merge-query observable-query pull-many]]
[auto-ap.datomic.accounts :as d-accounts]
[auto-ap.graphql.utils :refer [extract-client-ids]]
[auto-ap.permissions :refer [can?]]
[auto-ap.routes.invoice :as route]
[auto-ap.routes.payments :as payment-route]
[auto-ap.ssr-routes :as ssr-routes]
[auto-ap.ssr.components :as com]
[auto-ap.ssr.components.link-dropdown :refer [link-dropdown]]
[auto-ap.ssr.grid-page-helper :as helper]
[auto-ap.ssr.hx :as hx]
[auto-ap.ssr.invoice.common :refer [default-read]]
[auto-ap.ssr.pos.common :refer [date-range-field*]]
[auto-ap.ssr.svg :as svg]
[auto-ap.ssr.utils
:refer [clj-date-schema entity-id html-response main-transformer
ref->enum-schema strip wrap-implied-route-param]]
[auto-ap.time :as atime]
[auto-ap.utils :refer [dollars=]]
[bidi.bidi :as bidi]
[clj-time.coerce :as coerce]
[clojure.string :as str]
[datomic.api :as dc]
[hiccup.util :as hu]
[hiccup2.core :as hiccup]
[malli.core :as mc]))
(defn exact-match-id* [request]
(if (nat-int? (:exact-match-id (:query-params request)))
[:div {:x-data (hx/json {:exact_match (:exact-match-id (:query-params request))}) :id "exact-match-id-tag"}
(com/hidden {:name "exact-match-id"
"x-model" "exact_match"})
(com/pill {:color :primary}
[:span.inline-flex.space-x-2.items-center
[:div "exact match"]
[:div.w-3.h-3
(com/link {"@click" "exact_match=null; $nextTick(() => $dispatch('change'))"}
svg/x)]])]
[:div {:id "exact-match-id-tag"}]))
(defn filters [request]
[:form#invoice-filters {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms"
"hx-get" (bidi/path-for ssr-routes/only-routes
::route/table)
"hx-target" "#entity-table"
"hx-indicator" "#entity-table"}
(com/hidden {:name "status"
:value (some-> (:status (:query-params request)) name)})
[:fieldset.space-y-6
(com/field {:label "Vendor"}
(com/typeahead {:name "vendor"
:id "vendor"
:url (bidi/path-for ssr-routes/only-routes :vendor-search)
:value (:vendor (:query-params request))
:value-fn :db/id
:content-fn :vendor/name}))
(date-range-field* request)
(com/field {:label "Check #"}
(com/text-input {:name "check-number"
:id "check-number"
:class "hot-filter"
:value (:check-number (:query-params request))
:placeholder "e.g., 10001"
:size :small}))
(com/field {:label "Invoice #"}
(com/text-input {:name "invoice-number"
:id "invoice-number"
:class "hot-filter"
:value (:invoice-number (:query-params request))
:placeholder "e.g., ABC-456"
:size :small}))
(com/field {:label "Amount"}
[:div.flex.space-x-4.items-baseline
(com/money-input {:name "amount-gte"
:id "amount-gte"
:hx-preserve "true"
:class "hot-filter w-20"
:value (:amount-gte (:query-params request))
:placeholder "0.01"
:size :small})
[:div.align-baseline
"to"]
(com/money-input {:name "amount-lte"
:hx-preserve "true"
:id "amount-lte"
:class "hot-filter w-20"
:value (:amount-lte (:query-params request))
:placeholder "9999.34"
:size :small})])
(exact-match-id* request)]])
(defn fetch-ids [db {:keys [query-params route-params] :as request}]
(let [valid-clients (extract-client-ids (:clients request)
(:client-id request)
(when (:client-code request)
[:client/code (:client-code request)]))
query
(if (:exact-match-id query-params)
{:query {:find '[?e]
:in '[$ ?e [?c ...]]
:where '[[?e :invoice/client ?c]]}
:args [db
(:exact-match-id query-params)
valid-clients]}
(cond-> {:query {:find []
:in '[$ [?clients ?start ?end]]
:where '[[(iol-ion.query/scan-invoices $ ?clients ?start ?end) [[?e _ ?sort-default] ...]]]}
:args [db
[valid-clients
(some-> (:start-date query-params) coerce/to-date)
(some-> (:end-date query-params) coerce/to-date)]]}
(:client-id query-params)
(merge-query {:query {:in ['?client-id]
:where ['[?e :invoice/client ?client-id]]}
:args [(:client-id query-params)]})
(:client-code query-params)
(merge-query {:query {:in ['?client-code]
:where ['[?e :invoice/client ?client-id]
'[?client-id :client/code ?client-code]]}
:args [(:client-code query-params)]})
(:start (:due-range query-params)) (merge-query {:query {:in '[?start-due]
:where ['[?e :invoice/due ?due]
'[(>= ?due ?start-due)]]}
:args [(coerce/to-date (:start (:due-range query-params)))]})
(:end (:due-range query-params)) (merge-query {:query {:in '[?end-due]
:where ['[?e :invoice/due ?due]
'[(<= ?due ?end-due)]]}
:args [(coerce/to-date (:end (:due-range query-params)))]})
(:import-status query-params)
(merge-query {:query {:in ['?import-status]
:where ['[?e :invoice/import-status ?import-status]]}
:args [(:import-status query-params)]})
(:status route-params)
(merge-query {:query {:in ['?status]
:where ['[?e :invoice/status ?status]]}
:args [(:status route-params)]})
(:vendor query-params)
(merge-query {:query {:in ['?vendor-id]
:where ['[?e :invoice/vendor ?vendor-id]]}
:args [(:db/id (:vendor query-params))]})
(:account-id query-params)
(merge-query {:query {:in ['?account-id]
:where ['[?e :invoice/expense-accounts ?iea ?]
'[?iea :invoice-expense-account/account ?account-id]]}
:args [(:account-id query-params)]})
(:amount-gte query-params)
(merge-query {:query {:in ['?amount-gte]
:where ['[?e :invoice/total ?total-filter]
'[(>= ?total-filter ?amount-gte)]]}
:args [(:amount-gte query-params)]})
(:amount-lte query-params)
(merge-query {:query {:in ['?amount-lte]
:where ['[?e :invoice/total ?total-filter]
'[(<= ?total-filter ?amount-lte)]]}
:args [(:amount-lte query-params)]})
(not-empty (:invoice-number query-params))
(merge-query {:query {:in ['?invoice-number-like]
:where ['[?e :invoice/invoice-number ?invoice-number]
'[(.contains ^String ?invoice-number ?invoice-number-like)]]}
:args [(:invoice-number query-params)]})
(:scheduled-payments query-params)
(merge-query {:query {:in []
:where ['[?e :invoice/scheduled-payment]]}
:args []})
(:unresolved query-params)
(merge-query {:query {:in []
:where ['(or-join [?e]
(not [?e :invoice/expense-accounts])
(and [?e :invoice/expense-accounts ?ea]
(not [?ea :invoice-expense-account/account])))]}
:args []})
(seq (:location query-params))
(merge-query {:query {:in ['?location]
:where ['[?e :invoice/expense-accounts ?eas]
'[?eas :invoice-expense-account/location ?location]]}
:args [(:location query-params)]})
(:sort query-params) (add-sorter-fields {"client" ['[?e :invoice/client ?c]
'[?c :client/name ?sort-client]]
"vendor" ['[?e :invoice/vendor ?v]
'[?v :vendor/name ?sort-vendor]]
"description-original" ['[?e :transaction/description-original ?sort-description-original]]
"location" ['[?e :invoice/expense-accounts ?iea]
'[?iea :invoice-expense-account/location ?sort-location]]
"date" ['[?e :invoice/date ?sort-date]]
"due" ['[(get-else $ ?e :invoice/due #inst "2050-01-01") ?sort-due]]
"invoice-number" ['[?e :invoice/invoice-number ?sort-invoice-number]]
"total" ['[?e :invoice/total ?sort-total]]
"outstanding-balance" ['[?e :invoice/outstanding-balance ?sort-outstanding-balance]]}
query-params)
true
(merge-query {:query {:find ['?sort-default '?e]}})))]
(->> (observable-query query)
(apply-sort-3 (assoc query-params :default-asc? false))
(apply-pagination query-params))))
(defn hydrate-results [ids db _]
(let [results (->> (pull-many db default-read ids)
(group-by :db/id))
refunds (->> ids
(map results)
(map first))]
refunds))
(defn sum-outstanding [ids]
(->>
(dc/q {:find ['?id '?o]
:in ['$ '[?id ...]]
:where ['[?id :invoice/outstanding-balance ?o]]}
(dc/db conn)
ids)
(map last)
(reduce
+
0.0)))
(defn sum-total-amount [ids]
(->>
(dc/q {:find ['?id '?o]
:in ['$ '[?id ...]]
:where ['[?id :invoice/total ?o]]}
(dc/db conn)
ids)
(map last)
(reduce
+
0.0)))
(defn fetch-page [request]
(let [db (dc/db conn)
{ids-to-retrieve :ids matching-count :count
all-ids :all-ids} (fetch-ids db request)]
[(->> (hydrate-results ids-to-retrieve db request))
matching-count
(sum-outstanding all-ids)
(sum-total-amount all-ids)]))
(def query-schema (mc/schema
[:maybe [:map {:date-range [:date-range :start-date :end-date]}
[:sort {:optional true} [:maybe [:any]]]
[:per-page {:optional true :default 25} [:maybe :int]]
[:start {:optional true :default 0} [:maybe :int]]
[:amount-gte {:optional true} [:maybe :double]]
[:amount-lte {:optional true} [:maybe :double]]
[:vendor {:optional true :default nil} [:maybe [:entity-map {:pull [:db/id :vendor/name]}]]]
[:check-number {:optional true} [:maybe [:string {:decode/string strip}]]]
[:invoice-number {:optional true} [:maybe [:string {:decode/string strip}]]]
[:status {:optional true} [:maybe (ref->enum-schema "invoice-status")]]
[:exact-match-id {:optional true} [:maybe entity-id]]
[:all-selected {:optional true :default nil} [:maybe :boolean]]
[:selected {:optional true :default nil} [:maybe [:vector {:coerce? true}
entity-id]]]
[:start-date {:optional true}
[:maybe clj-date-schema]]
[:end-date {:optional true}
[:maybe clj-date-schema]]]]))
(comment
(mc/decode query-schema
{:start " "}
main-transformer))
(defn selected->ids [request params]
(let [all-selected (:all-selected params)
selected (:selected params)
ids (cond
all-selected
(:ids (fetch-ids (dc/db conn) (-> request
(assoc :query-params params)
(assoc-in [:query-params :start] 0)
(assoc-in [:query-params :per-page] 250))))
:else
selected)]
ids))
(defn pay-button* [params]
(let [ids (:ids params)
selected-client-count (if (seq ids)
(ffirst
(dc/q '[:find (count ?c)
:in $ [?i ...]
:where [?i :invoice/client ?c]]
(dc/db conn)
ids))
0)
vendor-totals (if (seq ids)
(->>
(dc/q '[:find ?i ?v ?ob
:in $ [?i ...]
:where [?i :invoice/vendor ?v]
[?i :invoice/outstanding-balance ?ob]]
(dc/db conn)
ids)
(reduce (fn [acc [_ v ob]]
(update acc v (fnil + 0) ob))
{})
(vals)))
all-credits-or-debits (or (every? #(<= % 0.0) vendor-totals)
(every? #(>= % 0.0) vendor-totals))
total (reduce + 0.0 vendor-totals)]
[:div {:hx-target "this"
:hx-get (bidi/path-for ssr-routes/only-routes
::route/pay-wizard)
:hx-trigger "click from:#pay-button"
:x-data (hx/json {:popper nil
:hovering false})
"x-init" "popper = Popper.createPopper($refs.button, $refs.tooltip, {placement: 'bottom', strategy: 'fixed', modifiers: [{name: 'preventOverflow'}, {name: 'offset', options: {offset: [0, 10]}}]});"}
(com/button {:color :primary
:id "pay-button"
:disabled (or (= (count (:ids params)) 0)
(not= 1 selected-client-count)
(not all-credits-or-debits))
"x-bind:hx-vals" "JSON.stringify({selected: $data.selected, 'all-selected': $data.all_selected})"
"hx-include" "#invoice-filters"
:hx-get (bidi/path-for ssr-routes/only-routes ::route/pay-button)
:hx-swap "outerHTML"
:hx-trigger "selectedChanged from:body, htmx:afterSwap from:#entity-table"
"@mouseover" "hovering=true; $nextTick(() => popper.update())"
"@mouseout" "hovering=false;"
:x-ref "button"
:minimal-loading? true
:class "relative"}
(if (> (count (:ids params)) 0)
(format "Pay %d invoices ($%,.2f)"
(count (:ids params))
(or total 0.0))
"Pay")
(when (or (= 0 (count ids))
(> selected-client-count 1))
(com/badge {} "!")))
[:div (hx/alpine-appear {:x-ref "tooltip"
:x-show "hovering"
:class "bg-gray-100 dark:bg-gray-600 rounded-lg shadow-2xl w-max z-50 p-4"})
(cond
(not all-credits-or-debits)
[:div "All vendor totals must be either positive or negative."]
(= 0 (count ids))
[:div "Please select some invoices to pay"]
(> selected-client-count 1)
[:div "Can only pay for one client at a time"]
:else
[:div "Click to choose a bank account"])]]))
(defn pay-button [request]
(html-response
(pay-button* {:ids (selected->ids request
(:query-params request))})))
;; TODO test as a real user
(def grid-page
(helper/build {:id "entity-table"
:nav com/main-aside-nav
:check-boxes? true
:page-specific-nav filters
:above-grid (fn [request]
(com/content-card {}
[:div.px-4.py-3.space-y-4
[:h1.text-2xl.mb-3.font-bold "Import new invoices"]
[:form.bg-blue-100.border-2.border-dashed.rounded-lg.border-blue-300.p-4.max-w-md.w-md.text-center.cursor-pointer
{:action (bidi/path-for ssr-routes/only-routes
::route/import-page)
:method "POST"
:id "upload"}
"Drop files to upload here"]
[:script
(hiccup/raw
"
ezcater_dropzone = new Dropzone (\"#upload\", {
success: function (file, response) {
document.getElementById(\"page-notification\").innerHTML = response;
document.getElementById(\"page-notification\").style[\"display\"] = \"block\";
},
acceptedFiles: '.xls,.xlsx,.pdf,.csv',
disablePreviews: true
});")]]))
:fetch-page fetch-page
:oob-render
(fn [request]
[(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)
(assoc-in (exact-match-id* request) [1 :hx-swap-oob] true)])
:query-schema query-schema
:parse-query-params (fn [p]
(mc/decode query-schema p main-transformer))
:action-buttons (fn [request]
(let [[_ _ outstanding total] (:page-results request)]
[(com/pill {:color :primary} "Outstanding: "
(format "$%,.2f" outstanding))
(com/pill {:color :secondary} "Total: "
(format "$%,.2f" total))
(when (can? (:identity request) {:subject :invoice :activity :bulk-delete})
(com/button {:hx-get (str (bidi/path-for ssr-routes/only-routes ::route/bulk-delete))
"x-bind:hx-vals" "JSON.stringify({selected: $data.selected, 'all-selected': $data.all_selected})"
"hx-include" "#invoice-filters"
:color :red}
"Void selected"))
(when (can? (:identity request) {:subject :invoice :activity :pay})
(pay-button* {:ids (selected->ids request
(:query-params request))}))
(when (can? (:identity request) {:subject :invoice :activity :create})
(com/button {:hx-get (bidi/path-for ssr-routes/only-routes ::route/new-wizard)}
"New invoice"))]))
:row-buttons (fn [request entity]
[(when (and (= :invoice-status/unpaid (:invoice/status entity))
(can? (:identity request) {:subject :invoice :activity :delete}))
(com/icon-button {:hx-delete (bidi/path-for ssr-routes/only-routes
::route/delete
:db/id (:db/id entity))
:hx-confirm "Are you sure you want to void this invoice?"}
svg/trash))
(when (and (can? (:identity request) {:subject :invoice :activity :edit})
(#{:invoice-status/unpaid :invoice-status/paid} (:invoice/status entity)))
(com/icon-button {:hx-put (bidi/path-for ssr-routes/only-routes
::route/edit-wizard
:db/id (:db/id entity))}
svg/pencil))
(when (and (can? (:identity request) {:subject :invoice :activity :edit})
(#{:invoice-status/voided} (:invoice/status entity)))
(com/icon-button {:hx-put (bidi/path-for ssr-routes/only-routes
::route/unvoid
:db/id (:db/id entity))}
svg/undo))])
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes ::route/page)}
"Invoices"]]
:title (fn [r]
(str
(some-> r :route-params :status name str/capitalize (str " "))
"Invoices"))
:entity-name "invoices"
:route ::route/table
:headers [{:key "client"
:name "Client"
:sort-key "client"
:hide? (fn [args]
(= (count (:clients args)) 1))
:render #(-> % :invoice/client :client/name)}
{:key "vendor"
:name "Vendor"
:sort-key "vendor"
:render #(-> % :invoice/vendor :vendor/name)}
{:key "invoice-number"
:name "Invoice number"
:sort-key "invoice-number"
:render :invoice/invoice-number}
{:key "date"
:sort-key "date"
:name "Date"
:show-starting "lg"
:render (fn [{:invoice/keys [date]}]
(some-> date (atime/unparse-local atime/normal-date)))}
{:key "status"
:name "Status"
:render (fn [{:invoice/keys [status]}]
(condp = status
:invoice-status/paid
(com/pill {:color :primary} "Paid")
:invoice-status/unpaid
(com/pill {:color :secondary} "Unpaid")
:invoice-status/voided
(com/pill {:color :red} "Voided")
nil
""))}
{:key "accounts"
:name "Account"
:show-starting "lg"
:render (fn [{:invoice/keys [expense-accounts client]}]
[:div.flex.flex-col.gap-y-2
(when (first expense-accounts)
[:div.flex-initial
(com/pill {:color :primary}
(:account/name
(d-accounts/clientize (dc/pull (dc/db conn) d-accounts/default-read (:db/id (:invoice-expense-account/account (first expense-accounts))))
(:db/id client))))])
(when (> (count expense-accounts) 1)
[:div.flex-initial
(com/pill {:color :secondary}
"+ " (dec (count expense-accounts)) " more")])])}
{:key "outstanding"
:name "Outstanding"
:sort-key "outstanding-balance"
:class "text-right"
:render (fn [{:invoice/keys [outstanding-balance total]}]
[:div
(some->> outstanding-balance (format "$%,.2f"))
(when-not (dollars= outstanding-balance total)
[:div.text-xs.text-gray-400 (format "of $%,.2f" total)])])}
{:key "links"
:name "Links"
:show-starting "lg"
:class "w-8"
:render (fn [i]
(link-dropdown
(concat (->> i
:invoice/payments
(filter (fn [p]
(not= :payment-status/voided
(:payment/status p))))
(mapcat (fn [p]
(cond-> [{:link (hu/url (bidi/path-for ssr-routes/only-routes
::payment-route/all-page)
{:exact-match-id (:db/id p)})
:content (str (format "$%,.2f" (:payment/amount p))
(some-> (:payment/date p) coerce/to-date-time (atime/unparse-local atime/normal-date) (#(str " payment on " %))))}]
(:payment/transaction p) (conj {:link (hu/url (bidi/path-for client-routes/routes :transactions)
{:exact-match-id (:db/id (first (:payment/transaction p)))})
:color :secondary
:content "Transaction"})))))
(when (:invoice/journal-entry i)
[{:link (hu/url (bidi/path-for client-routes/routes :ledger)
{:exact-match-id (:db/id (first (:invoice/journal-entry i)))})
:color :yellow
:content "Ledger entry"}])
(when (:invoice/source-url i)
[{:link (:invoice/source-url i)
:color :secondary
:content "File"}]))))}]}))
(def row* (partial helper/row* grid-page))
(def key->handler
{::route/import-page
(->
(helper/page-route grid-page :parse-query-params? false)
(wrap-implied-route-param :status nil))})

View File

@@ -33,6 +33,7 @@
[auto-ap.ssr.hx :as hx]
[auto-ap.ssr.invoice.common :refer [default-read]]
[auto-ap.ssr.invoice.new-invoice-wizard :as new-invoice-wizard]
[auto-ap.ssr.invoice.import :as invoice-import]
[auto-ap.ssr.pos.common :refer [date-range-field*]]
[auto-ap.ssr.svg :as svg]
[auto-ap.ssr.utils
@@ -1249,7 +1250,8 @@
(mm/wrap-decode-multi-form-state))
::route/table (helper/table-route grid-page :parse-query-params? false)}
(merge new-invoice-wizard/key->handler))
(merge new-invoice-wizard/key->handler)
(merge invoice-import/key->handler))
(fn [h]
(-> h
(wrap-copy-qp-pqp)

View File

@@ -2,7 +2,8 @@
(def routes {"" {:get ::all-page
"/unpaid" ::unpaid-page
"/paid" ::paid-page
"/voided" ::voided-page}
"/voided" ::voided-page
"/import" ::import-page}
"/new" {:get ::new-wizard
:post ::new-invoice-submit
:put ::new-invoice-submit

View File

@@ -74,6 +74,7 @@
(defn expense-accounts-field-v2 [{value :value on-change :on-change allowance :allowance expense-accounts :value client :client max-value :max locations :locations disabled :disabled percentage-only? :percentage-only? :or {percentage-only? false} vendor-id :vendor-id}]
[form-builder/virtual-builder {:value value
:schema schema