makes creation of receivable invoices as possible
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -4,6 +4,7 @@
|
||||
[auto-ap.routes.admin.clients :as ac-routes]
|
||||
[auto-ap.routes.admin.excel-invoices :as ei-routes]
|
||||
[auto-ap.routes.admin.import-batch :as ib-routes]
|
||||
[auto-ap.routes.outgoing-invoice :as oi-routes]
|
||||
[auto-ap.routes.admin.transaction-rules :as transaction-rules]
|
||||
[auto-ap.routes.admin.vendors :as v-routes]
|
||||
[auto-ap.routes.invoice :as invoice-route]
|
||||
@@ -82,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} (:matched-route request))
|
||||
(#{::invoice-route/all-page ::invoice-route/unpaid-page ::invoice-route/voided-page ::invoice-route/paid-page ::oi-routes/new} (:matched-route request))
|
||||
"invoices"
|
||||
|
||||
(#{:pos-sales :pos-expected-deposits :pos-tenders :pos-refunds :pos-cash-drawer-shifts} (:matched-route request))
|
||||
@@ -129,7 +130,12 @@
|
||||
{:date-range "month"})
|
||||
:active? (= ::invoice-route/voided-page (:matched-route request))
|
||||
:hx-boost "true"}
|
||||
"Voided"))
|
||||
"Voided")
|
||||
(menu-button- {:href (bidi/path-for ssr-routes/only-routes
|
||||
::oi-routes/new)
|
||||
:active? (= ::oi-routes/new (:matched-route request))
|
||||
:hx-boost "true"}
|
||||
"Create outgoing"))
|
||||
|
||||
(menu-button- {:icon svg/receipt-register-1
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
[auto-ap.ssr.pos.sales-orders :as pos-sales]
|
||||
[auto-ap.ssr.pos.tenders :as pos-tenders]
|
||||
[auto-ap.ssr.invoices :as invoice]
|
||||
[auto-ap.ssr.outgoing-invoice.new :as oin]
|
||||
[auto-ap.ssr.search :as search]
|
||||
[auto-ap.ssr.transaction.insights :as insights]
|
||||
[auto-ap.ssr.users :as users]
|
||||
@@ -100,5 +101,6 @@
|
||||
(into admin-clients/key->handler)
|
||||
(into admin-rules/key->handler)
|
||||
(into indicators/key->handler)
|
||||
(into payments/key->handler)))
|
||||
(into payments/key->handler)
|
||||
(into oin/route->handler)))
|
||||
|
||||
|
||||
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))})
|
||||
3
src/cljc/auto_ap/routes/outgoing_invoice.cljc
Normal file
3
src/cljc/auto_ap/routes/outgoing_invoice.cljc
Normal file
@@ -0,0 +1,3 @@
|
||||
(ns auto-ap.routes.outgoing-invoice)
|
||||
(def routes {"/" {"new" {:get ::new
|
||||
:post ::new-submit} }})
|
||||
@@ -4,6 +4,7 @@
|
||||
[auto-ap.routes.admin.import-batch :as ib-routes]
|
||||
[auto-ap.routes.indicators :as indicator-routes]
|
||||
[auto-ap.routes.admin.vendors :as v-routes]
|
||||
[auto-ap.routes.outgoing-invoice :as oi-routes]
|
||||
[auto-ap.routes.payments :as p-routes]
|
||||
[auto-ap.routes.invoice :as i-routes]
|
||||
[auto-ap.routes.admin.clients :as ac-routes]
|
||||
@@ -14,7 +15,7 @@
|
||||
"logout" :logout
|
||||
"search" :search
|
||||
"indicators" indicator-routes/routes
|
||||
|
||||
|
||||
"account" {"/search" {:get :account-search}}
|
||||
"admin" {"" :auto-ap.routes.admin/page
|
||||
"/client" ac-routes/routes
|
||||
@@ -64,6 +65,7 @@
|
||||
"/cash-drawer-shifts" {"" {:get :pos-cash-drawer-shifts}
|
||||
"/table" {:get :pos-cash-drawer-shift-table}}}
|
||||
|
||||
"outgoing-invoice" oi-routes/routes
|
||||
"payment" p-routes/routes
|
||||
"invoice" i-routes/routes
|
||||
"vendor" {"/search" :vendor-search}
|
||||
|
||||
Reference in New Issue
Block a user