Tweaks for editing invoices

This commit is contained in:
2024-04-02 20:52:40 -07:00
parent b7c6493bd6
commit 179e3b219b
4 changed files with 115 additions and 72 deletions

View File

@@ -39,58 +39,58 @@
(defn typeahead- [params]
[:div.relative {:x-data (hx/json {:open false
:baseUrl (if (str/includes? (:url params) "?")
(str (:url params) "&q=")
(str (:url params) "?q="))
:value {:value ((:value-fn params identity) (:value params)) :label ((:content-fn params identity) (:value params))}
:search ""
:active -1
:elements (if ((:value-fn params identity) (:value params))
[{:value ((:value-fn params identity) (:value params)) :label ((:content-fn params identity) (:value params))}]
[])
:popper nil
:baseUrl (if (str/includes? (:url params) "?")
(str (:url params) "&q=")
(str (:url params) "?q="))
:value {:value ((:value-fn params identity) (:value params)) :label ((:content-fn params identity) (:value params))}
:search ""
:active -1
:elements (if ((:value-fn params identity) (:value params))
[{:value ((:value-fn params identity) (:value params)) :label ((:content-fn params identity) (:value params))}]
[])
:popper nil
:warning_badge nil})
:x-modelable "value.value"
:x-model (:x-model params)
:x-init "popper = Popper.createPopper($refs.input, $refs.dropdown, {placement: 'bottom-start', strategy: 'fixed', modifiers: {name: 'offset', options: {offset: [0, 10]}}})
warning_badge = Popper.createPopper($refs.warning_badge, $refs.warning_pop, {placement: 'top', strategy: 'fixed', modifiers: {name: 'offset', options: {offset: [10,0 ]}}})"
}
[:a {:class (-> (hh/add-class (or (:class params) "") default-input-classes)
(hh/add-class "cursor-pointer"))
"@click.prevent" "open = !open; popper.update()"
"@keydown.down.prevent.stop" "open = true; popper.update()"
"@keydown.backspace" "value = {value: '', label: '' }"
:tabindex 0
:x-init (:x-init params)
:x-ref "input"
}
[:input (-> params
(dissoc :class)
(dissoc :value-fn)
(dissoc :content-fn)
:x-modelable "value.value"
:x-model (:x-model params)
:x-init "popper = Popper.createPopper($refs.input, $refs.dropdown, {placement: 'bottom-start', strategy: 'fixed', modifiers: {name: 'offset', options: {offset: [0, 10]}}})
warning_badge = Popper.createPopper($refs.warning_badge, $refs.warning_pop, {placement: 'top', strategy: 'fixed', modifiers: {name: 'offset', options: {offset: [10,0 ]}}})"}
(if (:disabled params)
[:span {:x-text "value.label"}]
[:a {:class (-> (hh/add-class (or (:class params) "") default-input-classes)
(hh/add-class "cursor-pointer"))
"@click.prevent" "open = !open; popper.update()"
"@keydown.down.prevent.stop" "open = true; popper.update()"
"@keydown.backspace" "value = {value: '', label: '' }"
:tabindex 0
:x-init (:x-init params)
:x-ref "input"}
[:input (-> params
(dissoc :class)
(dissoc :value-fn)
(dissoc :content-fn)
(dissoc :placeholder)
(dissoc :x-model)
(assoc
"x-ref" "hidden"
:type "hidden"
":value" "value.value"
:x-init (hiccup/raw (str "$watch('value', v => $dispatch('change')); "))))]
[:div.flex.w-full.justify-items-stretch
[:span.flex-grow.text-left {"x-text" "value.label"}]
[:div {:class "w-3 h-3 m-1 inline ml-1 justify-self-end text-gray-500 self-center"}
svg/drop-down]
[:div {:x-show "value.warning"
:x-ref "warning_badge"
:x-effect "if (value.warning) { $nextTick(()=> warning_badge.update()) }"}
(tags/badge- {:class "peer"} "!")
(dissoc :placeholder)
(dissoc :x-model)
(assoc
"x-ref" "hidden"
:type "hidden"
":value" "value.value"
:x-init (hiccup/raw (str "$watch('value', v => $dispatch('change')); "))))]
[:div.flex.w-full.justify-items-stretch
[:span.flex-grow.text-left {"x-text" "value.label"}]
[:div {:class "w-3 h-3 m-1 inline ml-1 justify-self-end text-gray-500 self-center"}
svg/drop-down]
[:div {:x-show "value.warning"
:x-ref "warning_badge"
:x-effect "if (value.warning) { $nextTick(()=> warning_badge.update()) }"}
(tags/badge- {:class "peer"} "!")
[:div {:x-show "value.warning"
:x-ref "warning_pop"
:class "hidden peer-hover:block bg-red-50 dark:bg-gray-600 rounded-lg shadow-2xl w-max z-50 p-4"
:x-text "value.warning"}
]]]]
[:div {:x-show "value.warning"
:x-ref "warning_pop"
:class "hidden peer-hover:block bg-red-50 dark:bg-gray-600 rounded-lg shadow-2xl w-max z-50 p-4"
:x-text "value.warning"}]]]])
[:ul.dropdown-contents {:class "bg-gray-100 dark:bg-gray-600 rounded-lg shadow-2xl w-max z-50 ring-1"
"x-ref" "dropdown"
@@ -104,12 +104,12 @@
"x-show " "open"
"x-trap" "open"
"@click.outside" "open=false;"}
[:input {:type "text"
:class (-> (:class params)
(or "")
(hh/add-class default-input-classes)
(hh/replace-wildcard ["rounded" "border"] "border-bottom bg-gray-100 rounded-t-lg w-full"))
(or "")
(hh/add-class default-input-classes)
(hh/replace-wildcard ["rounded" "border"] "border-bottom bg-gray-100 rounded-t-lg w-full"))
"x-model" "search"
"placeholder" (:placeholder params)
"@keydown.down.prevent" "active ++; active = active >= elements.length - 1 ? elements.length - 1 : active"
@@ -127,8 +127,7 @@
"x-html" "element.label"}]]]
[:template {:x-if "elements.length == 0"}
[:li {:class "px-4 py-2 flex gap-2 items-center outline-0 focus:bg-neutral-100 hover:bg-neutral-100 whitespace-nowrap [&.active]:bg-primary-500 text-gray-800 dark:text-gray-100 text-xs "}
"No results found"]]]
]])
"No results found"]]]]])
(defn use-size [size]

View File

@@ -2,10 +2,10 @@
(:require [auto-ap.datomic
:refer [audit-transact conn pull-attr]]
[auto-ap.datomic.accounts :as d-accounts]
[auto-ap.ssr.invoice.common :refer [default-read]]
[auto-ap.datomic.invoices :as d-invoices]
[auto-ap.graphql.utils :refer [assert-can-see-client
assert-not-locked exception->4xx]]
[auto-ap.logging :as alog]
[auto-ap.routes.invoice :as route]
[auto-ap.routes.utils
:refer [wrap-client-redirect-unauthenticated]]
@@ -22,7 +22,6 @@
[auto-ap.ssr.utils
:refer [->db-id apply-middleware-to-all-handlers clj-date-schema
entity-id form-validation-error html-response money strip
->OOBElements
wrap-schema-enforce]]
[auto-ap.time :as atime]
[bidi.bidi :as bidi]
@@ -70,10 +69,15 @@
(defn check-vendor-default-account [vendor-id]
(some? (:vendor/default-account (get-vendor vendor-id))))
;; TODO negative expense accounts for negative invoices?
;; TODO make changing expense accounts work for editing invoices
;; TODO make a change of total automatically change the expense accounts when editing
;; TODO just close when editing, don't go to print screen
(def new-form-schema
[:map
[:db/id {:optional true} [:maybe entity-id]]
[:customize-due-and-scheduled? {:optional true :default false :decode/arbitrary (fn [x] (if (= "" x)
false
x))} [:maybe :boolean]]
@@ -81,10 +85,11 @@
[:invoice/date clj-date-schema]
[:invoice/due {:optional true} [:maybe clj-date-schema]]
[:invoice/scheduled-payment {:optional true} [:maybe clj-date-schema]]
[:invoice/vendor [:and entity-id
[:fn {:error/message "Vendor is missing default expense account"}
check-vendor-default-account]]]
[:invoice/invoice-number [:string {:min 1 :decode/string strip}]]
[:invoice/vendor {:optional true}
[:and entity-id
[:fn {:error/message "Vendor is missing default expense account"}
check-vendor-default-account]]]
[:invoice/invoice-number {:optional true} [:string {:min 1 :decode/string strip}]]
[:invoice/total money]
[:invoice/expense-accounts
[:vector {:coerce? true}
@@ -98,6 +103,15 @@
[:fn {:error/fn (fn [r x] (:type r))
:error/path [:invoice-expense-account/location]} check-invoice-expense-account-location]]]]])
(defn wrap-schema [s]
[:and s
[:fn (fn [{:keys [:db/id :invoice/invoice-number :invoice/vendor] :as z}]
(println "HERE" id invoice-number vendor z)
(if id
true
(and invoice-number vendor)))]])
(defn clientize-vendor [{:vendor/keys [terms-overrides automatically-paid-when-due default-account account-overrides] :as vendor} client-id]
(let [terms-override (->> terms-overrides
(filter (fn [to]
@@ -139,7 +153,7 @@
[])
(step-schema [_]
(mut/select-keys (mm/form-schema linear-wizard) #{:invoice/client :invoice/vendor :invoice/date :invoice/due :invoice/scheduled-payment :invoice/total :invoice/invoice-number :customize-due-and-scheduled?}))
(wrap-schema (mut/select-keys (mm/form-schema linear-wizard) #{:invoice/client :invoice/vendor :invoice/date :invoice/due :invoice/scheduled-payment :invoice/total :invoice/invoice-number :db/id :customize-due-and-scheduled?})))
(render-step [this request]
(mm/default-render-step
@@ -157,6 +171,12 @@
:scheduledPayment (some-> (fc/field-value (:invoice/scheduled-payment fc/*current*))
(atime/unparse-local atime/normal-date))
:customizeDueAndScheduled (fc/field-value (:customize-due-and-scheduled? fc/*current*))})}
(fc/with-field :db/id
(when (fc/field-value)
(com/hidden {:name (fc/field-name)
:value (fc/field-value)})))
(fc/with-field :customize-due-and-scheduled?
(com/hidden {:name (fc/field-name)
:value (fc/field-value)
@@ -184,6 +204,7 @@
[:div.w-96
(com/typeahead {:name (fc/field-name)
:error? (fc/error?)
:disabled (boolean (-> request :multi-form-state :snapshot :db/id))
:class "w-96"
:placeholder "Search..."
:url (bidi/path-for ssr-routes/only-routes :vendor-search)
@@ -254,6 +275,7 @@
:errors (fc/field-errors)}
[:div {:class "w-24"}
(com/text-input {:value (-> (fc/field-value))
:disabled (boolean (-> request :multi-form-state :snapshot :db/id))
:name (fc/field-name)
:error? (fc/field-errors)
:placeholder "HA-123"})]))
@@ -302,7 +324,7 @@
[:div.flex.flex-col
(com/typeahead {:name name
:placeholder "Search..."
:url (hu/url (bidi/path-for ssr-routes/only-routes :account-search)
:url (hu/url (bidi/path-for ssr-routes/only-routes :account-search)
{:client-id client-id
:purpose "invoice"})
:id name
@@ -468,7 +490,7 @@
:body (mm/default-step-body
{}
[:p.text-lg "Would you like to pay this invoice now?"]
(com/navigation-button-list {}
(com/navigation-button (-> {:class "w-48"
:hx-get (hu/url (bidi.bidi/path-for ssr-routes/only-routes ::route/pay-wizard)
@@ -481,7 +503,7 @@
hx/trigger-click-or-enter) "Add another")
(com/navigation-button {:class "w-48" :next-arrow? false
"@click" "$dispatch('modalclose') "
"@keyup.enter.stop" "$dispatch('modalclose')"}
"@keyup.enter.stop" "$dispatch('modalclose')"}
"Close")))
:footer
nil
@@ -561,15 +583,16 @@
:accounts (->AccountsStep this)
:next-steps (->NextSteps this)}
step-key)))
(form-schema [_] new-form-schema)
(form-schema [_]
new-form-schema)
(submit [this {:keys [multi-form-state request-method identity] :as request}]
(let [invoice (:snapshot multi-form-state)
client-id (->db-id (:invoice/client invoice))
vendor-id (->db-id (:invoice/vendor invoice))
transaction [:upsert-invoice (-> multi-form-state
:snapshot
(assoc :db/id "invoice")
(dissoc :customize-due-and-scheduled?)
(assoc :db/id (or (:db/id invoice) "invoice"))
(dissoc :customize-due-and-scheduled? :invoice/journal-entry :invoice/payments)
(assoc :invoice/expense-accounts (if-let [ieas (seq (-> multi-form-state :snapshot :invoice/expense-accounts))]
ieas
[{:db/id "123"
@@ -585,15 +608,15 @@
(update :invoice/date coerce/to-date)
(update :invoice/due coerce/to-date))]]
(assert-invoice-amounts-add-up (second transaction))
(assert-no-conflicting invoice)
(when-not (:db/id invoice)
(assert-no-conflicting invoice))
(exception->4xx #(assert-can-see-client (:identity request) client-id))
(exception->4xx #(assert-not-locked client-id (:invoice/date invoice)))
(let [transaction-result (audit-transact [transaction] (:identity request))]
(solr/touch-with-ledger (get-in transaction-result [:tempids "invoice"]))
(assoc-in (mm/render-navigate {:request (assoc-in request [:multi-form-state :snapshot :db/id] (get-in transaction-result [:tempids "invoice"]))
:to-step :next-steps
})
:to-step :next-steps})
[:headers "hx-trigger"] "invalidated")))))
(def new-wizard (->NewWizard2 nil nil))
@@ -604,6 +627,17 @@
[]
{:invoice/date (time/now)}))
(defn initial-edit-wizard-state [request]
(let [entity (dc/pull (dc/db conn) default-read (:db/id (:route-params request)))
entity (select-keys entity (mut/keys new-form-schema))]
(mm/->MultiStepFormState entity
[]
entity)))
(defn location-select [{{:keys [name account-id client-id value] :as qp} :query-params}]
(html-response (location-select* {:name name
:value value
@@ -676,6 +710,10 @@
{::route/new-wizard (-> mm/open-wizard-handler
(mm/wrap-wizard new-wizard)
(mm/wrap-init-multi-form-state initial-new-wizard-state))
::route/edit-wizard (-> mm/open-wizard-handler
(mm/wrap-wizard new-wizard)
(mm/wrap-init-multi-form-state initial-edit-wizard-state)
(wrap-schema-enforce :route-schema [:map [:db/id entity-id]]))
::route/due-date (-> due-date
(mm/wrap-wizard new-wizard)
(mm/wrap-decode-multi-form-state)

View File

@@ -407,7 +407,11 @@
::route/delete
:db/id (:db/id entity))
:hx-confirm "Are you sure you want to void this invoice?"}
svg/trash))])
svg/trash))
(com/icon-button {:hx-put (bidi/path-for ssr-routes/only-routes
::route/edit-wizard
:db/id (:db/id entity)) }
svg/pencil)])
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes ::route/page)}
"Invoices"]]
:title (fn [r]

View File

@@ -12,13 +12,15 @@
"/account/location-select" ::location-select
"/account/prediction" ::account-prediction
"/total" ::expense-account-total}
"/pay-button" ::pay-button
"/pay" {:get ::pay-wizard
"/navigate" ::pay-wizard-navigate
:post ::pay-submit}
"/bulk-delete" {:get ::bulk-delete
:delete ::bulk-delete-confirm}
["/" [#"\d+" :db/id]] {:delete ::delete}
["/" [#"\d+" :db/id]] {:delete ::delete
"/edit" ::edit-wizard}
"/table" ::table
"/glimpse" {"" {:get :invoice-glimpse