Tweaks for editing invoices
This commit is contained in:
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user