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.clients :as ac-routes]
|
||||||
[auto-ap.routes.admin.excel-invoices :as ei-routes]
|
[auto-ap.routes.admin.excel-invoices :as ei-routes]
|
||||||
[auto-ap.routes.admin.import-batch :as ib-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.transaction-rules :as transaction-rules]
|
||||||
[auto-ap.routes.admin.vendors :as v-routes]
|
[auto-ap.routes.admin.vendors :as v-routes]
|
||||||
[auto-ap.routes.invoice :as invoice-route]
|
[auto-ap.routes.invoice :as invoice-route]
|
||||||
@@ -82,7 +83,7 @@
|
|||||||
|
|
||||||
(defn main-aside-nav- [request]
|
(defn main-aside-nav- [request]
|
||||||
(let [selected (cond
|
(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"
|
"invoices"
|
||||||
|
|
||||||
(#{:pos-sales :pos-expected-deposits :pos-tenders :pos-refunds :pos-cash-drawer-shifts} (:matched-route request))
|
(#{:pos-sales :pos-expected-deposits :pos-tenders :pos-refunds :pos-cash-drawer-shifts} (:matched-route request))
|
||||||
@@ -129,7 +130,12 @@
|
|||||||
{:date-range "month"})
|
{:date-range "month"})
|
||||||
:active? (= ::invoice-route/voided-page (:matched-route request))
|
:active? (= ::invoice-route/voided-page (:matched-route request))
|
||||||
:hx-boost "true"}
|
: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
|
(menu-button- {:icon svg/receipt-register-1
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
[auto-ap.ssr.pos.sales-orders :as pos-sales]
|
[auto-ap.ssr.pos.sales-orders :as pos-sales]
|
||||||
[auto-ap.ssr.pos.tenders :as pos-tenders]
|
[auto-ap.ssr.pos.tenders :as pos-tenders]
|
||||||
[auto-ap.ssr.invoices :as invoice]
|
[auto-ap.ssr.invoices :as invoice]
|
||||||
|
[auto-ap.ssr.outgoing-invoice.new :as oin]
|
||||||
[auto-ap.ssr.search :as search]
|
[auto-ap.ssr.search :as search]
|
||||||
[auto-ap.ssr.transaction.insights :as insights]
|
[auto-ap.ssr.transaction.insights :as insights]
|
||||||
[auto-ap.ssr.users :as users]
|
[auto-ap.ssr.users :as users]
|
||||||
@@ -100,5 +101,6 @@
|
|||||||
(into admin-clients/key->handler)
|
(into admin-clients/key->handler)
|
||||||
(into admin-rules/key->handler)
|
(into admin-rules/key->handler)
|
||||||
(into indicators/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.admin.import-batch :as ib-routes]
|
||||||
[auto-ap.routes.indicators :as indicator-routes]
|
[auto-ap.routes.indicators :as indicator-routes]
|
||||||
[auto-ap.routes.admin.vendors :as v-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.payments :as p-routes]
|
||||||
[auto-ap.routes.invoice :as i-routes]
|
[auto-ap.routes.invoice :as i-routes]
|
||||||
[auto-ap.routes.admin.clients :as ac-routes]
|
[auto-ap.routes.admin.clients :as ac-routes]
|
||||||
@@ -14,7 +15,7 @@
|
|||||||
"logout" :logout
|
"logout" :logout
|
||||||
"search" :search
|
"search" :search
|
||||||
"indicators" indicator-routes/routes
|
"indicators" indicator-routes/routes
|
||||||
|
|
||||||
"account" {"/search" {:get :account-search}}
|
"account" {"/search" {:get :account-search}}
|
||||||
"admin" {"" :auto-ap.routes.admin/page
|
"admin" {"" :auto-ap.routes.admin/page
|
||||||
"/client" ac-routes/routes
|
"/client" ac-routes/routes
|
||||||
@@ -64,6 +65,7 @@
|
|||||||
"/cash-drawer-shifts" {"" {:get :pos-cash-drawer-shifts}
|
"/cash-drawer-shifts" {"" {:get :pos-cash-drawer-shifts}
|
||||||
"/table" {:get :pos-cash-drawer-shift-table}}}
|
"/table" {:get :pos-cash-drawer-shift-table}}}
|
||||||
|
|
||||||
|
"outgoing-invoice" oi-routes/routes
|
||||||
"payment" p-routes/routes
|
"payment" p-routes/routes
|
||||||
"invoice" i-routes/routes
|
"invoice" i-routes/routes
|
||||||
"vendor" {"/search" :vendor-search}
|
"vendor" {"/search" :vendor-search}
|
||||||
|
|||||||
Reference in New Issue
Block a user