makes creation of receivable invoices as possible
This commit is contained in:
238
src/clj/auto_ap/ssr/outgoing_invoice/new.clj
Normal file
238
src/clj/auto_ap/ssr/outgoing_invoice/new.clj
Normal file
@@ -0,0 +1,238 @@
|
||||
(ns auto-ap.ssr.outgoing-invoice.new
|
||||
(:require [amazonica.aws.lambda :as lambda]
|
||||
[auto-ap.datomic :refer [conn pull-attr]]
|
||||
[auto-ap.routes.invoice :as invoice-route]
|
||||
[auto-ap.routes.outgoing-invoice :as route]
|
||||
[auto-ap.routes.utils :refer [wrap-client-redirect-unauthenticated
|
||||
wrap-secure]]
|
||||
[auto-ap.ssr-routes :as ssr-routes]
|
||||
[auto-ap.ssr.components :as com]
|
||||
[auto-ap.ssr.form-cursor :as fc]
|
||||
[auto-ap.ssr.grid-page-helper :refer [wrap-trim-client-ids]]
|
||||
[auto-ap.ssr.nested-form-params :refer [wrap-nested-form-params]]
|
||||
[auto-ap.ssr.ui :refer [base-page]]
|
||||
[auto-ap.ssr.utils :refer [clj-date-schema modal-response money
|
||||
percentage strip wrap-schema-decode]]
|
||||
[auto-ap.time :as atime]
|
||||
[bidi.bidi :as bidi]
|
||||
[clojure.data.json :as json]
|
||||
[datomic.api :as dc]
|
||||
[malli.core :as mc]))
|
||||
|
||||
(def form-schema (mc/schema [:map
|
||||
[:outgoing-invoice/client [:entity-map {:pull '[:client/name {:client/address [*]}]}]]
|
||||
[:outgoing-invoice/date clj-date-schema]
|
||||
[:outgoing-invoice/to :string]
|
||||
[:outgoing-invoice/invoice-number :string]
|
||||
[:outgoing-invoice/tax percentage]
|
||||
[:outgoing-invoice/to-address [:map
|
||||
[:street1 :string]
|
||||
[:street2 {:optional true} [:maybe [ :string { :decode/string strip}]]]
|
||||
[:city :string]
|
||||
[:state :string]
|
||||
[:zip :string]]]
|
||||
[:outgoing-invoice/line-items
|
||||
[:vector {:coerce? true}
|
||||
[:map
|
||||
[:outgoing-invoice-line-item/description :string]
|
||||
[:outgoing-invoice-line-item/unit-price money]
|
||||
[:outgoing-invoice-line-item/quantity money]]]]]))
|
||||
|
||||
(defn form* [{:keys [form-params form-errors]}]
|
||||
(fc/start-form
|
||||
form-params
|
||||
form-errors
|
||||
[:form {:hx-post (bidi.bidi/path-for ssr-routes/only-routes
|
||||
::route/new-submit)}
|
||||
(fc/with-field :outgoing-invoice/invoice-number
|
||||
(com/validated-field {:errors (fc/field-errors)
|
||||
:label "Invoice #"}
|
||||
[:div.w-96
|
||||
(com/text-input {:name (fc/field-name)
|
||||
:autofocus true
|
||||
:error? (fc/error?)
|
||||
:class "w-96"
|
||||
:placeholder "10000"
|
||||
:value (fc/field-value)})]))
|
||||
(fc/with-field :outgoing-invoice/date
|
||||
(com/validated-field {:errors (fc/field-errors)
|
||||
:label "Date"}
|
||||
[:div.w-96
|
||||
(com/date-input {:name (fc/field-name)
|
||||
:error? (fc/error?)
|
||||
:class "w-96"
|
||||
:placeholder "10000"
|
||||
:value (-> (fc/field-value)
|
||||
(atime/unparse-local atime/normal-date))})]))
|
||||
[:div.flex.gap-4
|
||||
(fc/with-field :outgoing-invoice/client
|
||||
(com/validated-field {:errors (fc/field-errors)
|
||||
:label "From (client)"}
|
||||
[:div.w-96
|
||||
(com/typeahead {:name (fc/field-name)
|
||||
:error? (fc/error?)
|
||||
:class "w-96"
|
||||
:placeholder "Search..."
|
||||
:url (bidi/path-for ssr-routes/only-routes :company-search)
|
||||
:value (fc/field-value)
|
||||
:content-fn (fn [c] (pull-attr (dc/db conn) :client/name c))})]))
|
||||
[:div.flex.flex-col.gap-2
|
||||
(fc/with-field :outgoing-invoice/to
|
||||
(com/validated-field {:errors (fc/field-errors)
|
||||
:label "To"}
|
||||
[:div.w-96
|
||||
(com/text-input {:name (fc/field-name)
|
||||
:error? (fc/error?)
|
||||
:class "w-96"
|
||||
:placeholder "Hello Company"
|
||||
:value (fc/field-value)})]))
|
||||
|
||||
(fc/with-field-default :outgoing-invoice/to-address {}
|
||||
(list
|
||||
(fc/with-field :address/street1
|
||||
(com/validated-field {:label "Address"
|
||||
:errors (fc/field-errors)}
|
||||
(com/text-input {:name (fc/field-name)
|
||||
:error? (fc/error?)
|
||||
:class "w-full"
|
||||
:placeholder "1200 Pennsylvania Avenue"
|
||||
:value (fc/field-value)})))
|
||||
(fc/with-field :address/street2
|
||||
(com/validated-field {:errors (fc/field-errors)}
|
||||
(com/text-input {:name (fc/field-name)
|
||||
:error? (fc/error?)
|
||||
:class "w-full"
|
||||
:placeholder "Suite 300"
|
||||
:value (fc/field-value)})))
|
||||
[:div.flex.w-full.space-x-4
|
||||
(fc/with-field :address/city
|
||||
(com/validated-field {:errors (fc/field-errors)
|
||||
:class "w-full grow shrink"}
|
||||
(com/text-input {:name (fc/field-name)
|
||||
:error? (fc/error?)
|
||||
:class "w-full"
|
||||
:placeholder "Cupertino"
|
||||
:value (fc/field-value)})))
|
||||
(fc/with-field :address/state
|
||||
(com/validated-field {:errors (fc/field-errors)
|
||||
:class "w-16 shrink-0"}
|
||||
(com/text-input {:name (fc/field-name)
|
||||
:error? (fc/error?)
|
||||
:class "w-full"
|
||||
:placeholder "CA"
|
||||
:value (fc/field-value)})))
|
||||
(fc/with-field :address/zip
|
||||
(com/validated-field {:errors (fc/field-errors)
|
||||
:class "w-24 shrink-0"}
|
||||
(com/text-input {:name (fc/field-name)
|
||||
:error? (fc/error?)
|
||||
:placeholder "98101"
|
||||
:class "w-full"
|
||||
:value (fc/field-value)})))]))]]
|
||||
|
||||
|
||||
(fc/with-field-default :outgoing-invoice/line-items [{} {} {}]
|
||||
(com/validated-field {:errors (fc/field-errors)
|
||||
:label "Line items"}
|
||||
(com/data-grid {:headers [(com/data-grid-header {} "Description")
|
||||
(com/data-grid-header {} "Quantity")
|
||||
(com/data-grid-header {} "Unit Price")]}
|
||||
(fc/cursor-map
|
||||
(fn [z]
|
||||
(com/data-grid-row
|
||||
{}
|
||||
(com/data-grid-cell
|
||||
{}
|
||||
(fc/with-field :outgoing-invoice-line-item/description
|
||||
(com/text-input {:name (fc/field-name)
|
||||
:value (fc/field-value)
|
||||
:class "w-full"
|
||||
:placeholder "Catered sandwiches"})))
|
||||
(com/data-grid-cell
|
||||
{}
|
||||
(fc/with-field :outgoing-invoice-line-item/quantity
|
||||
(com/money-input {:name (fc/field-name)
|
||||
:value (fc/field-value)
|
||||
:placeholder "20"})))
|
||||
(com/data-grid-cell
|
||||
{}
|
||||
(fc/with-field :outgoing-invoice-line-item/unit-price
|
||||
(com/money-input {:name (fc/field-name)
|
||||
:value (fc/field-value)
|
||||
:placeholder "23.50"})))))))))
|
||||
(fc/with-field-default :outgoing-invoice/tax 10.0
|
||||
|
||||
(com/validated-field {:errors (fc/field-errors)
|
||||
:label "Tax %"}
|
||||
(com/money-input {:name (fc/field-name)
|
||||
:value (fc/field-value)})))
|
||||
|
||||
[:div.flex.flex-row-reverse (com/button {:color :primary :class "w-24"} "Generate")]]))
|
||||
|
||||
(defn submit [{:keys [form-params]}]
|
||||
(let [line-items (->> form-params
|
||||
:outgoing-invoice/line-items
|
||||
(filter (fn [li] (not-empty (:outgoing-invoice-line-item/description li))))
|
||||
(mapv
|
||||
#(assoc % :outgoing-invoice-line-item/total (* (:outgoing-invoice-line-item/unit-price %)
|
||||
(:outgoing-invoice-line-item/quantity %)))))
|
||||
|
||||
subtotal (reduce + 0.0 (map :outgoing-invoice-line-item/total line-items))
|
||||
tax (* subtotal (:outgoing-invoice/tax form-params))
|
||||
total (+ subtotal tax)
|
||||
result
|
||||
|
||||
(-> (lambda/invoke {:function-name "genpdf" :payload
|
||||
(json/write-str
|
||||
(-> form-params
|
||||
(assoc :outgoing-invoice/line-items
|
||||
line-items
|
||||
:outgoing-invoice/total total
|
||||
:outgoing-invoice/tax tax)
|
||||
(update :outgoing-invoice/date
|
||||
#(some-> % (atime/unparse-local atime/normal-date)))))})
|
||||
:payload
|
||||
slurp
|
||||
json/read-str)]
|
||||
(modal-response
|
||||
(com/modal {}
|
||||
(com/modal-card {} [:div "Download your invoice"] [:div
|
||||
"click "
|
||||
(com/link {:href (str "https://data.prod.app.integreatconsult.com/" result)}
|
||||
"here")
|
||||
" to download"] [:div])))))
|
||||
|
||||
(def page (-> (fn page [{:keys [identity] :as request}]
|
||||
(base-page
|
||||
request
|
||||
(com/page {:nav com/main-aside-nav
|
||||
:client-selection (:client-selection (:session request))
|
||||
:clients (:clients request)
|
||||
:client (:client request)
|
||||
:identity (:identity request)
|
||||
:request request}
|
||||
(apply com/breadcrumbs {} [[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
::invoice-route/all-page)}
|
||||
"Invoices"]
|
||||
[:a {:href "#"} ;; TODO
|
||||
"Outgoing"]
|
||||
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||
::route/new)}
|
||||
"New"]])
|
||||
(com/content-card {}
|
||||
[:div {:class "flex flex-col px-4 py-3 space-y-3 lg:flex-row lg:items-center lg:justify-between lg:space-y-0 lg:space-x-4 text-gray-800 dark:text-gray-100"}
|
||||
[:div
|
||||
[:h1.text-2xl.mb-3.font-bold "New outgoing invoice"]]
|
||||
[:div {:class "flex flex-col flex-shrink-0 space-y-3 md:flex-row md:items-center lg:justify-end md:space-y-0 md:space-x-3"}]]
|
||||
[:div.px-4
|
||||
(form* request)]))
|
||||
"New outgoing invoice"))
|
||||
(wrap-trim-client-ids)
|
||||
(wrap-secure)
|
||||
(wrap-client-redirect-unauthenticated)))
|
||||
|
||||
(def route->handler
|
||||
{::route/new page
|
||||
::route/new-submit (-> submit
|
||||
(wrap-schema-decode :form-schema form-schema)
|
||||
(wrap-nested-form-params))})
|
||||
Reference in New Issue
Block a user