check printing
This commit is contained in:
@@ -364,6 +364,10 @@
|
|||||||
:db/valueType :db.type/ref
|
:db/valueType :db.type/ref
|
||||||
:db/cardinality :db.cardinality/one
|
:db/cardinality :db.cardinality/one
|
||||||
:db/doc "The bank account that was used to pay"}
|
:db/doc "The bank account that was used to pay"}
|
||||||
|
{:db/ident :payment/invoices
|
||||||
|
:db/valueType :db.type/ref
|
||||||
|
:db/cardinality :db.cardinality/many
|
||||||
|
:db/doc "Any invoices this payment is related to"}
|
||||||
|
|
||||||
;; enums
|
;; enums
|
||||||
{:db/ident :payment-status/pending}
|
{:db/ident :payment-status/pending}
|
||||||
|
|||||||
36
src/clj/auto_ap/datomic/bank_accounts.clj
Normal file
36
src/clj/auto_ap/datomic/bank_accounts.clj
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
(ns auto-ap.datomic.bank-accounts
|
||||||
|
(:require [datomic.api :as d]
|
||||||
|
[auto-ap.datomic :refer [uri]]
|
||||||
|
[clj-time.coerce :as c]
|
||||||
|
[clojure.set :refer [rename-keys]]))
|
||||||
|
|
||||||
|
(defn add-arg [query name value where & rest]
|
||||||
|
(let [query (-> query
|
||||||
|
(update :args conj value)
|
||||||
|
(update-in [:query :in] conj name)
|
||||||
|
(update-in [:query :where] conj where))]
|
||||||
|
(reduce #(update-in %1 [:query :where] conj %2) query rest)))
|
||||||
|
|
||||||
|
(def default-read '(pull ?e [*]))
|
||||||
|
|
||||||
|
(defn <-datomic [x]
|
||||||
|
(->> x
|
||||||
|
(map #(update % :bank-account/type :db/ident))
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defn get-by-id [id]
|
||||||
|
(->>
|
||||||
|
(d/query (-> {:query {:find [default-read]
|
||||||
|
:in ['$]
|
||||||
|
:where []}
|
||||||
|
:args [(d/db (d/connect uri))]}
|
||||||
|
(add-arg '?e (cond-> id (string? id) Long/parseLong) ['?e])))
|
||||||
|
(map first)
|
||||||
|
(<-datomic)
|
||||||
|
(first)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -14,3 +14,16 @@
|
|||||||
(map (fn [ba]
|
(map (fn [ba]
|
||||||
(update ba :bank-account/type :db/ident ))
|
(update ba :bank-account/type :db/ident ))
|
||||||
bas)))))))
|
bas)))))))
|
||||||
|
|
||||||
|
(defn get-by-id [id]
|
||||||
|
(->>
|
||||||
|
(d/query (-> {:query {:find ['(pull ?e [*])]
|
||||||
|
:in ['$ '?e]
|
||||||
|
:where [['?e]]}
|
||||||
|
:args [(d/db (d/connect uri)) (cond-> id (string? id) Long/parseLong)]}
|
||||||
|
))
|
||||||
|
(first)
|
||||||
|
(first)
|
||||||
|
#_(map first)
|
||||||
|
|
||||||
|
#_(first)))
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
(defn <-datomic [x]
|
(defn <-datomic [x]
|
||||||
(->> x
|
(->> x
|
||||||
(map first)
|
|
||||||
|
|
||||||
(map #(update % :invoice/date c/from-date))
|
(map #(update % :invoice/date c/from-date))
|
||||||
(map #(update % :invoice/status :db/ident))
|
(map #(update % :invoice/status :db/ident))
|
||||||
@@ -42,6 +42,7 @@
|
|||||||
'[?c :client/original-id ?original-id])
|
'[?c :client/original-id ?original-id])
|
||||||
(:status args) (add-arg '?status (keyword "invoice-status" (:status args))
|
(:status args) (add-arg '?status (keyword "invoice-status" (:status args))
|
||||||
'[?e :invoice/status ?status])))
|
'[?e :invoice/status ?status])))
|
||||||
|
(map first)
|
||||||
(<-datomic)))
|
(<-datomic)))
|
||||||
|
|
||||||
(defn sort-fn [args]
|
(defn sort-fn [args]
|
||||||
@@ -75,9 +76,23 @@
|
|||||||
:where []}
|
:where []}
|
||||||
:args [(d/db (d/connect uri))]}
|
:args [(d/db (d/connect uri))]}
|
||||||
(add-arg '?e (cond-> id (string? id) Long/parseLong) ['?e])))
|
(add-arg '?e (cond-> id (string? id) Long/parseLong) ['?e])))
|
||||||
|
(map first)
|
||||||
(<-datomic)
|
(<-datomic)
|
||||||
(first)))
|
(first)))
|
||||||
|
|
||||||
|
(defn get-multi [ids]
|
||||||
|
(->>
|
||||||
|
(d/query {:query {:find [[default-read '...]]
|
||||||
|
:in ['$ ['?e '...]]
|
||||||
|
:where [['?e]]}
|
||||||
|
:args [(d/db (d/connect uri))
|
||||||
|
(mapv #(cond-> % (string? %) Long/parseLong)
|
||||||
|
ids)] }
|
||||||
|
|
||||||
|
|
||||||
|
)
|
||||||
|
(<-datomic)))
|
||||||
|
|
||||||
(defn find-conflicting [{:keys [:invoice/invoice-number :invoice/vendor :invoice/client]}]
|
(defn find-conflicting [{:keys [:invoice/invoice-number :invoice/vendor :invoice/client]}]
|
||||||
(->> (d/query
|
(->> (d/query
|
||||||
(cond-> {:query {:find [default-read]
|
(cond-> {:query {:find [default-read]
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
[auto-ap.datomic.vendors :as d-vendors]
|
[auto-ap.datomic.vendors :as d-vendors]
|
||||||
[auto-ap.db.users :as users]
|
[auto-ap.db.users :as users]
|
||||||
[auto-ap.db.checks :as checks]
|
[auto-ap.db.checks :as checks]
|
||||||
[auto-ap.routes.checks :as rchecks]
|
|
||||||
[auto-ap.graphql.users :as gq-users]
|
[auto-ap.graphql.users :as gq-users]
|
||||||
[auto-ap.graphql.checks :as gq-checks]
|
[auto-ap.graphql.checks :as gq-checks]
|
||||||
[auto-ap.graphql.expense-accounts :as expense-accounts]
|
[auto-ap.graphql.expense-accounts :as expense-accounts]
|
||||||
@@ -291,9 +290,9 @@
|
|||||||
:mutations
|
:mutations
|
||||||
{:print_checks {:type :check_result
|
{:print_checks {:type :check_result
|
||||||
:args {:invoice_payments {:type '(list :invoice_payment_amount)}
|
:args {:invoice_payments {:type '(list :invoice_payment_amount)}
|
||||||
:bank_account_id {:type 'Int}
|
:bank_account_id {:type 'String}
|
||||||
:type {:type 'String}
|
:type {:type 'String}
|
||||||
:company_id {:type 'Int}}
|
:client_id {:type 'String}}
|
||||||
:resolve :mutation/print-checks}
|
:resolve :mutation/print-checks}
|
||||||
|
|
||||||
:add_handwritten_check {:type :check_result
|
:add_handwritten_check {:type :check_result
|
||||||
@@ -454,14 +453,14 @@
|
|||||||
|
|
||||||
(defn print-checks [context args value]
|
(defn print-checks [context args value]
|
||||||
|
|
||||||
(assert-can-see-company (:id context) (:company_id args))
|
(assert-can-see-company (:id context) (:client_id args))
|
||||||
(->graphql
|
(->graphql
|
||||||
(rchecks/print-checks (map (fn [i] {:invoice-id (:invoice_id i)
|
(gq-checks/print-checks (map (fn [i] {:invoice-id (:invoice_id i)
|
||||||
:amount (:amount i)})
|
:amount (:amount i)})
|
||||||
(:invoice_payments args))
|
(:invoice_payments args))
|
||||||
(:company_id args)
|
(:client_id args)
|
||||||
(:bank_account_id args)
|
(:bank_account_id args)
|
||||||
(:type args))))
|
(:type args))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,13 +8,289 @@
|
|||||||
[auto-ap.db.invoices-checks :as invoices-checks]
|
[auto-ap.db.invoices-checks :as invoices-checks]
|
||||||
[auto-ap.db.checks :as checks]
|
[auto-ap.db.checks :as checks]
|
||||||
[auto-ap.datomic.checks :as d-checks]
|
[auto-ap.datomic.checks :as d-checks]
|
||||||
|
[auto-ap.datomic.invoices :as d-invoices]
|
||||||
|
[auto-ap.datomic.vendors :as d-vendors]
|
||||||
|
[auto-ap.datomic.clients :as d-clients]
|
||||||
|
[auto-ap.datomic.bank-accounts :as d-bank-accounts]
|
||||||
|
[auto-ap.db.invoices-checks :as invoices-checks]
|
||||||
[auto-ap.db.vendors :as vendors]
|
[auto-ap.db.vendors :as vendors]
|
||||||
[auto-ap.db.invoices :as invoices]
|
[auto-ap.db.invoices :as invoices]
|
||||||
|
[auto-ap.db.utils :refer [query]]
|
||||||
[auto-ap.datomic :refer [uri]]
|
[auto-ap.datomic :refer [uri]]
|
||||||
[auto-ap.utils :refer [by]]
|
[auto-ap.utils :refer [by]]
|
||||||
|
[auto-ap.numeric :refer [num->words]]
|
||||||
|
[config.core :refer [env]]
|
||||||
[auto-ap.db.companies :as companies]
|
[auto-ap.db.companies :as companies]
|
||||||
[auto-ap.time :refer [parse normal-date iso-date]]))
|
[auto-ap.time :refer [parse normal-date iso-date local-now]]
|
||||||
|
[amazonica.aws.s3 :as s3]
|
||||||
|
[clojure.string :as str]
|
||||||
|
[clj-pdf.core :as pdf]
|
||||||
|
[clj-time.format :as f]
|
||||||
|
[clj-time.core :as time]
|
||||||
|
[clojure.java.io :as io])
|
||||||
|
(:import [java.text DecimalFormat]
|
||||||
|
[java.util UUID]
|
||||||
|
[java.io ByteArrayOutputStream]))
|
||||||
|
|
||||||
|
|
||||||
|
(def parser (f/formatter "MM/dd/YYYY"))
|
||||||
|
(defn date->str [t]
|
||||||
|
(f/unparse parser t))
|
||||||
|
|
||||||
|
(defn distribute [nums]
|
||||||
|
(let [sum (reduce + 0 nums)]
|
||||||
|
(map #(* 100 (/ % sum)) nums)))
|
||||||
|
|
||||||
|
(defn split-memo [memo]
|
||||||
|
(str/join "\n"
|
||||||
|
(reverse
|
||||||
|
(take 6
|
||||||
|
(concat
|
||||||
|
(reduce
|
||||||
|
(fn [[line & rest ] word]
|
||||||
|
(let [line (or line "")]
|
||||||
|
(if (> (+ (count line) (count word)) 50)
|
||||||
|
(concat [word line] rest)
|
||||||
|
(concat [(str line " " word)]
|
||||||
|
rest))))
|
||||||
|
[]
|
||||||
|
(str/split memo #" "))
|
||||||
|
(repeat ""))))))
|
||||||
|
|
||||||
|
(defn make-check-pdf [check]
|
||||||
|
(let [output-stream (ByteArrayOutputStream.)]
|
||||||
|
(pdf/pdf
|
||||||
|
[{:left-margin 25 :right-margin 0 :top-margin 0 :bottom-margin 0 :size :letter}
|
||||||
|
(let [{:keys [paid-to company check date amount memo] {print-as :print-as vendor-name :name :as vendor} :vendor} check
|
||||||
|
df (DecimalFormat. "#,###.00")
|
||||||
|
word-amount (num->words amount)
|
||||||
|
|
||||||
|
amount (str "--" (.format df amount) "--")]
|
||||||
|
|
||||||
|
[:table {:num-cols 12 :border false :leading 11 :widths (distribute [2 3 3 3 3 3 3 3 3 2 2 2])}
|
||||||
|
[(let [{:keys [name bank] {:keys [street1 street2 city state zip ]} :address} company]
|
||||||
|
[:cell {:colspan 3 } [:paragraph {:leading 14} name "\n" street1 "\n" (str city ", " state " " zip)] ])
|
||||||
|
(let [{{:keys [name acct]} :bank} company]
|
||||||
|
[:cell {:colspan 7 :align :center} [:paragraph {:style :bold} name] [:paragraph {:size 8 :leading 8} acct]])
|
||||||
|
[:cell {:colspan 2 :size 13}
|
||||||
|
check]]
|
||||||
|
|
||||||
|
[[:cell {:colspan 9}]
|
||||||
|
[:cell {:colspan 3 :leading -10} date]]
|
||||||
|
[[:cell {:colspan 12 :size 14}]
|
||||||
|
]
|
||||||
|
|
||||||
|
[[:cell {:size 13 :leading 13} "PAY"]
|
||||||
|
[:cell {:size 8 :leading 8 } "TO THE ORDER OF"]
|
||||||
|
[:cell {:colspan 7} (if (seq print-as)
|
||||||
|
print-as
|
||||||
|
vendor-name)]
|
||||||
|
[:cell {:colspan 3} amount]]
|
||||||
|
|
||||||
|
[[:cell {}]
|
||||||
|
[:cell {:colspan 8} (str " -- " word-amount " " (str/join "" (take (max
|
||||||
|
2
|
||||||
|
(- 97
|
||||||
|
(count word-amount)))
|
||||||
|
(repeat "-"))))
|
||||||
|
[:line {:line-width 0.15 :color [50 50 50]}]]
|
||||||
|
[:cell {:colspan 3}]]
|
||||||
|
|
||||||
|
|
||||||
|
[[:cell {:size 9 :leading 11.5} "\n\n\n\n\nMEMO"]
|
||||||
|
[:cell {:colspan 5 :leading 11.5} (split-memo memo)
|
||||||
|
[:line {:line-width 0.15 :color [50 50 50]}]]
|
||||||
|
[:cell {:colspan 6 } (if (:signature-file company)
|
||||||
|
[:image { :top-margin 90 :xscale 0.30 :yscale 0.30 :align :center}
|
||||||
|
|
||||||
|
(:signature-file company)]
|
||||||
|
[:spacer])]]
|
||||||
|
|
||||||
|
#_[
|
||||||
|
#_[:cell {:colspan 5} #_memo ]
|
||||||
|
#_[:cell {:colspan 6}]]
|
||||||
|
|
||||||
|
[[:cell {:colspan 2}]
|
||||||
|
[:cell {:colspan 10 :leading 30}
|
||||||
|
[:phrase {:size 18 :ttf-name "public/micrenc.ttf"} (str "c" check "c a" (:routing (:bank company)) "a " (:acct-number (:bank company)) "c")]]]
|
||||||
|
|
||||||
|
[[:cell {:colspan 12 :leading 18} [:spacer]]]
|
||||||
|
|
||||||
|
[[:cell]
|
||||||
|
(into
|
||||||
|
[:cell {:colspan 9}]
|
||||||
|
(let [{:keys [name]
|
||||||
|
{:keys [street1 city state zip bank]} :address} company]
|
||||||
|
(list
|
||||||
|
[:paragraph " " name]
|
||||||
|
[:paragraph " " street1]
|
||||||
|
[:paragraph " " city ", " state " " zip]
|
||||||
|
)))
|
||||||
|
[:cell {:colspan 2 :size 13}
|
||||||
|
check]]
|
||||||
|
|
||||||
|
[[:cell {:colspan 12 :leading 74} [:spacer]]]
|
||||||
|
|
||||||
|
[[:cell]
|
||||||
|
[:cell {:colspan 5} [:paragraph
|
||||||
|
" " vendor-name "\n"
|
||||||
|
" " (:street1 (:address vendor)) "\n"
|
||||||
|
" " (:city (:address vendor)) ", " (:state (:address vendor)) " " (:zip (:address vendor))]]
|
||||||
|
[:cell {:align :right}
|
||||||
|
"Paid to:\n"
|
||||||
|
"Amount:\n"
|
||||||
|
"Date:\n"]
|
||||||
|
|
||||||
|
[:cell {:colspan 5}
|
||||||
|
[:paragraph paid-to]
|
||||||
|
[:paragraph amount]
|
||||||
|
[:paragraph date]]]
|
||||||
|
|
||||||
|
[[:cell {:colspan 3} "Memo:"]
|
||||||
|
[:cell {:colspan 9} memo]]
|
||||||
|
|
||||||
|
[[:cell {:colspan 12} [:spacer]]]
|
||||||
|
[[:cell {:colspan 12} [:spacer]]]
|
||||||
|
[[:cell {:colspan 12} [:spacer]]]
|
||||||
|
[[:cell {:colspan 12} [:spacer]]]
|
||||||
|
|
||||||
|
[[:cell {:colspan 5}]
|
||||||
|
[:cell {:align :right :colspan 2}
|
||||||
|
"Check:\n"
|
||||||
|
"Vendor:\n"
|
||||||
|
"Bank Account:\n"
|
||||||
|
"Paid To:\n"
|
||||||
|
"Amount:\n"
|
||||||
|
"Date:\n"]
|
||||||
|
|
||||||
|
[:cell {:colspan 5}
|
||||||
|
[:paragraph check]
|
||||||
|
[:paragraph vendor-name]
|
||||||
|
[:paragraph (:name (:bank company))]
|
||||||
|
[:paragraph paid-to]
|
||||||
|
[:paragraph amount]
|
||||||
|
[:paragraph date]]]
|
||||||
|
[[:cell {:colspan 3} "Memo:"]
|
||||||
|
[:cell {:colspan 9} memo]]
|
||||||
|
])]
|
||||||
|
output-stream)
|
||||||
|
(.toByteArray output-stream)))
|
||||||
|
|
||||||
|
(defn make-pdfs [checks]
|
||||||
|
(loop [[check & checks] checks]
|
||||||
|
(when check
|
||||||
|
(s3/put-object :bucket-name (:data-bucket env)
|
||||||
|
:key (:s3-key check)
|
||||||
|
:input-stream (-> check
|
||||||
|
:pdf-data
|
||||||
|
make-check-pdf
|
||||||
|
(io/make-input-stream {}))
|
||||||
|
:metadata {:content-type "application/pdf"})
|
||||||
|
(recur checks))))
|
||||||
|
|
||||||
|
(defn merge-pdfs [keys]
|
||||||
|
(let [merged-pdf-stream (java.io.ByteArrayOutputStream.)
|
||||||
|
uuid (str (UUID/randomUUID))]
|
||||||
|
(apply pdf/collate (concat [{:size :letter} merged-pdf-stream] (->> keys
|
||||||
|
(map #(s3/get-object (:data-bucket env) %))
|
||||||
|
(map :input-stream))))
|
||||||
|
(s3/put-object :bucket-name (:data-bucket env)
|
||||||
|
:key (str "merged-checks/" uuid ".pdf")
|
||||||
|
:input-stream (io/make-input-stream (.toByteArray merged-pdf-stream) {})
|
||||||
|
:metadata {:content-length (count (.toByteArray merged-pdf-stream))
|
||||||
|
:content-type "application/pdf"})
|
||||||
|
(str "http://" (:data-bucket env) ".s3-website-us-east-1.amazonaws.com/merged-checks/" uuid ".pdf")))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
(defmulti check-for-invoices (fn [invoices vendor-id vendors company bank-account type index invoice-amounts]
|
||||||
|
type))
|
||||||
|
|
||||||
|
(defmethod check-for-invoices "check" [invoices vendor-id vendors company bank-account type index invoice-amounts]
|
||||||
|
(let [uuid (str (UUID/randomUUID))
|
||||||
|
vendor (vendors vendor-id)
|
||||||
|
|
||||||
|
|
||||||
|
memo (str "Invoice #'s: "
|
||||||
|
(str/join ", "
|
||||||
|
(map (fn [i]
|
||||||
|
(str (:invoice/invoice-number i) "(" (invoice-amounts (:db/id i)) ")"))
|
||||||
|
invoices)))]
|
||||||
|
|
||||||
|
{:payment/s3-uuid uuid
|
||||||
|
:payment/s3-key (str "checks/" uuid ".pdf")
|
||||||
|
:payment/s3-url (str "http://" (:data-bucket env) ".s3-website-us-east-1.amazonaws.com/checks/" uuid ".pdf")
|
||||||
|
:payment/check-number (+ index (:bank-account/check-number bank-account))
|
||||||
|
:payment/type :payment-type/check
|
||||||
|
:payment/bank-account (:db/id bank-account)
|
||||||
|
:payment/amount (reduce + 0 (map (comp invoice-amounts :db/id) invoices))
|
||||||
|
:payment/memo memo
|
||||||
|
:payment/vendor (:db/id vendor)
|
||||||
|
:payment/client (:db/id company)
|
||||||
|
:payment/date (time/now)
|
||||||
|
#_#_:payment/pdf-data {:vendor vendor
|
||||||
|
:paid-to (or (:vendor/paid-to vendor) (:vendor/name vendor))
|
||||||
|
:amount (reduce + 0 (map (comp invoice-amounts :db/id) invoices))
|
||||||
|
:check (str (+ index (:bank-account/check-number bank-account)))
|
||||||
|
:memo memo
|
||||||
|
:date (date->str (local-now))
|
||||||
|
:company {:name (:name company)
|
||||||
|
:address (:address company)
|
||||||
|
:signature-file (:signature-file company)
|
||||||
|
:bank {:name (:bank-account/bank-name bank-account)
|
||||||
|
:acct (:bank-account/bank-code bank-account)
|
||||||
|
:routing (:bank-account/routing bank-account)
|
||||||
|
:acct-number (:bank-account/number bank-account)}}}
|
||||||
|
|
||||||
|
:payment/invoices (map :db/id invoices)}))
|
||||||
|
|
||||||
|
(defmethod check-for-invoices "debit" [invoices vendor-id vendors company bank-account type index invoice-amounts]
|
||||||
|
(let [vendor (vendors vendor-id)]
|
||||||
|
{:type "debit"
|
||||||
|
:bank-account-id (:db/id bank-account)
|
||||||
|
:amount (reduce + 0 (map (comp invoice-amounts :db/id) invoices))
|
||||||
|
:status "cleared"
|
||||||
|
:memo "Debit"
|
||||||
|
:vendor-id (:db/id vendor)
|
||||||
|
:client-id (:db/id company)
|
||||||
|
:date (time/now)
|
||||||
|
:invoices (map :db/id invoices)}))
|
||||||
|
|
||||||
|
(defmethod check-for-invoices "cash" [invoices vendor-id vendors company bank-account type index invoice-amounts]
|
||||||
|
(let [vendor (vendors vendor-id)]
|
||||||
|
{:type "cash"
|
||||||
|
:bank-account-id (:db/id bank-account)
|
||||||
|
:amount (reduce + 0 (map (comp invoice-amounts :db/id) invoices))
|
||||||
|
:status "cleared"
|
||||||
|
:memo "Cash"
|
||||||
|
:vendor-id (:db/id vendor)
|
||||||
|
:client-id (:db/id company)
|
||||||
|
:date (time/now)
|
||||||
|
:invoices (map :db/id invoices)}))
|
||||||
|
|
||||||
|
(defn print-checks [invoice-payments client-id bank-account-id type]
|
||||||
|
|
||||||
|
(let [client-id (Long/parseLong client-id)
|
||||||
|
bank-account-id (Long/parseLong bank-account-id)
|
||||||
|
invoices (d-invoices/get-multi (map :invoice-id invoice-payments))
|
||||||
|
client (d-clients/get-by-id client-id)
|
||||||
|
vendors (by :db/id (d-vendors/get-graphql {}))
|
||||||
|
invoice-amounts (by (comp #(Long/parseLong %) :invoice-id) :amount invoice-payments)
|
||||||
|
invoices-grouped-by-vendor (group-by (comp :db/id :invoice/vendor) invoices)
|
||||||
|
|
||||||
|
bank-account (d-bank-accounts/get-by-id bank-account-id)
|
||||||
|
checks (-> (for [[[vendor-id invoices] index] (map vector invoices-grouped-by-vendor (range))]
|
||||||
|
[invoices (check-for-invoices invoices vendor-id vendors client bank-account type index invoice-amounts)])
|
||||||
|
doall)]
|
||||||
|
|
||||||
|
(clojure.pprint/pprint checks)
|
||||||
|
@(d/transact (d/connect uri) (map second checks))
|
||||||
|
|
||||||
|
|
||||||
|
{:invoices [] #_(invoices/get-multi (map :id (mapcat first checks)))
|
||||||
|
:pdf-url nil #_(if (= type "check")
|
||||||
|
(merge-pdfs (map (comp :s3-key second) checks))
|
||||||
|
nil)}))
|
||||||
|
|
||||||
(defn get-payment-page [context args value]
|
(defn get-payment-page [context args value]
|
||||||
(let [args (assoc args :id (:id context))
|
(let [args (assoc args :id (:id context))
|
||||||
@@ -55,7 +331,7 @@
|
|||||||
(let [check (d-checks/get-by-id id)]
|
(let [check (d-checks/get-by-id id)]
|
||||||
(assert (or (= :payment-status/pending (:payment/status check))
|
(assert (or (= :payment-status/pending (:payment/status check))
|
||||||
(#{:payment-type/cash :payment-type/debit} (:payment/type check))))
|
(#{:payment-type/cash :payment-type/debit} (:payment/type check))))
|
||||||
(assert-can-see-company (:id context) (:company-id check))
|
(assert-can-see-company (:id context) (:client-id check))
|
||||||
(let [removing-payments (mapcat (fn [x]
|
(let [removing-payments (mapcat (fn [x]
|
||||||
(let [invoice (:invoice-payment/invoice x)
|
(let [invoice (:invoice-payment/invoice x)
|
||||||
new-balance (+ (:invoice/outstanding-balance invoice)
|
new-balance (+ (:invoice/outstanding-balance invoice)
|
||||||
|
|||||||
@@ -1,305 +1,9 @@
|
|||||||
(ns auto-ap.routes.checks
|
(ns auto-ap.routes.checks
|
||||||
(:require [auto-ap.db.companies :as companies]
|
(:require
|
||||||
[auto-ap.db.vendors :as vendors]
|
|
||||||
[auto-ap.db.invoices :as invoices]
|
|
||||||
[auto-ap.graphql.utils :refer [assert-can-see-company]]
|
|
||||||
[auto-ap.utils :refer [by]]
|
|
||||||
[auto-ap.numeric :refer [num->words]]
|
|
||||||
[auto-ap.db.checks :as checks]
|
|
||||||
[auto-ap.db.invoices-checks :as invoices-checks]
|
|
||||||
[auto-ap.db.utils :refer [query]]
|
|
||||||
[auto-ap.parse :as parse]
|
|
||||||
[amazonica.aws.s3 :as s3]
|
|
||||||
[hiccup.core :refer [html]]
|
[hiccup.core :refer [html]]
|
||||||
[auto-ap.routes.utils :refer [wrap-secure]]
|
[auto-ap.routes.utils :refer [wrap-secure]]
|
||||||
[auto-ap.time :refer [local-now]]
|
[compojure.core :refer [GET POST context defroutes
|
||||||
[config.core :refer [env]]
|
wrap-routes]]))
|
||||||
[compojure.core :refer [GET POST context defroutes
|
|
||||||
wrap-routes]]
|
|
||||||
[clojure.string :as str]
|
|
||||||
[clj-pdf.core :as pdf]
|
|
||||||
[clj-time.format :as f]
|
|
||||||
[clj-time.core :as time]
|
|
||||||
[clojure.java.io :as io])
|
|
||||||
(:import [java.text DecimalFormat]
|
|
||||||
[java.util UUID]
|
|
||||||
[java.io ByteArrayOutputStream]))
|
|
||||||
|
|
||||||
(def parser (f/formatter "MM/dd/YYYY"))
|
|
||||||
(defn date->str [t]
|
|
||||||
(f/unparse parser t))
|
|
||||||
|
|
||||||
(defn distribute [nums]
|
|
||||||
(let [sum (reduce + 0 nums)]
|
|
||||||
(map #(* 100 (/ % sum)) nums)))
|
|
||||||
|
|
||||||
(defn split-memo [memo]
|
|
||||||
(str/join "\n"
|
|
||||||
(reverse
|
|
||||||
(take 6
|
|
||||||
(concat
|
|
||||||
(reduce
|
|
||||||
(fn [[line & rest ] word]
|
|
||||||
(let [line (or line "")]
|
|
||||||
(if (> (+ (count line) (count word)) 50)
|
|
||||||
(concat [word line] rest)
|
|
||||||
(concat [(str line " " word)]
|
|
||||||
rest))))
|
|
||||||
[]
|
|
||||||
(str/split memo #" "))
|
|
||||||
(repeat ""))))))
|
|
||||||
|
|
||||||
(defn make-check-pdf [check]
|
|
||||||
(let [output-stream (ByteArrayOutputStream.)]
|
|
||||||
(pdf/pdf
|
|
||||||
[{:left-margin 25 :right-margin 0 :top-margin 0 :bottom-margin 0 :size :letter}
|
|
||||||
(let [{:keys [paid-to company check date amount memo] {print-as :print-as vendor-name :name :as vendor} :vendor} check
|
|
||||||
df (DecimalFormat. "#,###.00")
|
|
||||||
word-amount (num->words amount)
|
|
||||||
|
|
||||||
amount (str "--" (.format df amount) "--")]
|
|
||||||
|
|
||||||
[:table {:num-cols 12 :border false :leading 11 :widths (distribute [2 3 3 3 3 3 3 3 3 2 2 2])}
|
|
||||||
[(let [{:keys [name bank] {:keys [street1 street2 city state zip ]} :address} company]
|
|
||||||
[:cell {:colspan 3 } [:paragraph {:leading 14} name "\n" street1 "\n" (str city ", " state " " zip)] ])
|
|
||||||
(let [{{:keys [name acct]} :bank} company]
|
|
||||||
[:cell {:colspan 7 :align :center} [:paragraph {:style :bold} name] [:paragraph {:size 8 :leading 8} acct]])
|
|
||||||
[:cell {:colspan 2 :size 13}
|
|
||||||
check]]
|
|
||||||
|
|
||||||
[[:cell {:colspan 9}]
|
|
||||||
[:cell {:colspan 3 :leading -10} date]]
|
|
||||||
[[:cell {:colspan 12 :size 14}]
|
|
||||||
]
|
|
||||||
|
|
||||||
[[:cell {:size 13 :leading 13} "PAY"]
|
|
||||||
[:cell {:size 8 :leading 8 } "TO THE ORDER OF"]
|
|
||||||
[:cell {:colspan 7} (if (seq print-as)
|
|
||||||
print-as
|
|
||||||
vendor-name)]
|
|
||||||
[:cell {:colspan 3} amount]]
|
|
||||||
|
|
||||||
[[:cell {}]
|
|
||||||
[:cell {:colspan 8} (str " -- " word-amount " " (str/join "" (take (max
|
|
||||||
2
|
|
||||||
(- 97
|
|
||||||
(count word-amount)))
|
|
||||||
(repeat "-"))))
|
|
||||||
[:line {:line-width 0.15 :color [50 50 50]}]]
|
|
||||||
[:cell {:colspan 3}]]
|
|
||||||
|
|
||||||
|
|
||||||
[[:cell {:size 9 :leading 11.5} "\n\n\n\n\nMEMO"]
|
|
||||||
[:cell {:colspan 5 :leading 11.5} (split-memo memo)
|
|
||||||
[:line {:line-width 0.15 :color [50 50 50]}]]
|
|
||||||
[:cell {:colspan 6 } (if (:signature-file company)
|
|
||||||
[:image { :top-margin 90 :xscale 0.30 :yscale 0.30 :align :center}
|
|
||||||
|
|
||||||
(:signature-file company)]
|
|
||||||
[:spacer])]]
|
|
||||||
|
|
||||||
#_[
|
|
||||||
#_[:cell {:colspan 5} #_memo ]
|
|
||||||
#_[:cell {:colspan 6}]]
|
|
||||||
|
|
||||||
[[:cell {:colspan 2}]
|
|
||||||
[:cell {:colspan 10 :leading 30}
|
|
||||||
[:phrase {:size 18 :ttf-name "public/micrenc.ttf"} (str "c" check "c a" (:routing (:bank company)) "a " (:acct-number (:bank company)) "c")]]]
|
|
||||||
|
|
||||||
[[:cell {:colspan 12 :leading 18} [:spacer]]]
|
|
||||||
|
|
||||||
[[:cell]
|
|
||||||
(into
|
|
||||||
[:cell {:colspan 9}]
|
|
||||||
(let [{:keys [name]
|
|
||||||
{:keys [street1 city state zip bank]} :address} company]
|
|
||||||
(list
|
|
||||||
[:paragraph " " name]
|
|
||||||
[:paragraph " " street1]
|
|
||||||
[:paragraph " " city ", " state " " zip]
|
|
||||||
)))
|
|
||||||
[:cell {:colspan 2 :size 13}
|
|
||||||
check]]
|
|
||||||
|
|
||||||
[[:cell {:colspan 12 :leading 74} [:spacer]]]
|
|
||||||
|
|
||||||
[[:cell]
|
|
||||||
[:cell {:colspan 5} [:paragraph
|
|
||||||
" " vendor-name "\n"
|
|
||||||
" " (:street1 (:address vendor)) "\n"
|
|
||||||
" " (:city (:address vendor)) ", " (:state (:address vendor)) " " (:zip (:address vendor))]]
|
|
||||||
[:cell {:align :right}
|
|
||||||
"Paid to:\n"
|
|
||||||
"Amount:\n"
|
|
||||||
"Date:\n"]
|
|
||||||
|
|
||||||
[:cell {:colspan 5}
|
|
||||||
[:paragraph paid-to]
|
|
||||||
[:paragraph amount]
|
|
||||||
[:paragraph date]]]
|
|
||||||
|
|
||||||
[[:cell {:colspan 3} "Memo:"]
|
|
||||||
[:cell {:colspan 9} memo]]
|
|
||||||
|
|
||||||
[[:cell {:colspan 12} [:spacer]]]
|
|
||||||
[[:cell {:colspan 12} [:spacer]]]
|
|
||||||
[[:cell {:colspan 12} [:spacer]]]
|
|
||||||
[[:cell {:colspan 12} [:spacer]]]
|
|
||||||
|
|
||||||
[[:cell {:colspan 5}]
|
|
||||||
[:cell {:align :right :colspan 2}
|
|
||||||
"Check:\n"
|
|
||||||
"Vendor:\n"
|
|
||||||
"Bank Account:\n"
|
|
||||||
"Paid To:\n"
|
|
||||||
"Amount:\n"
|
|
||||||
"Date:\n"]
|
|
||||||
|
|
||||||
[:cell {:colspan 5}
|
|
||||||
[:paragraph check]
|
|
||||||
[:paragraph vendor-name]
|
|
||||||
[:paragraph (:name (:bank company))]
|
|
||||||
[:paragraph paid-to]
|
|
||||||
[:paragraph amount]
|
|
||||||
[:paragraph date]]]
|
|
||||||
[[:cell {:colspan 3} "Memo:"]
|
|
||||||
[:cell {:colspan 9} memo]]
|
|
||||||
])]
|
|
||||||
output-stream)
|
|
||||||
(.toByteArray output-stream)))
|
|
||||||
|
|
||||||
(defn make-pdfs [checks]
|
|
||||||
(loop [[check & checks] checks]
|
|
||||||
(when check
|
|
||||||
(s3/put-object :bucket-name (:data-bucket env)
|
|
||||||
:key (:s3-key check)
|
|
||||||
:input-stream (-> check
|
|
||||||
:pdf-data
|
|
||||||
make-check-pdf
|
|
||||||
(io/make-input-stream {}))
|
|
||||||
:metadata {:content-type "application/pdf"})
|
|
||||||
(recur checks))))
|
|
||||||
|
|
||||||
(defn merge-pdfs [keys]
|
|
||||||
(let [merged-pdf-stream (java.io.ByteArrayOutputStream.)
|
|
||||||
uuid (str (UUID/randomUUID))]
|
|
||||||
(apply pdf/collate (concat [{:size :letter} merged-pdf-stream] (->> keys
|
|
||||||
(map #(s3/get-object (:data-bucket env) %))
|
|
||||||
(map :input-stream))))
|
|
||||||
(s3/put-object :bucket-name (:data-bucket env)
|
|
||||||
:key (str "merged-checks/" uuid ".pdf")
|
|
||||||
:input-stream (io/make-input-stream (.toByteArray merged-pdf-stream) {})
|
|
||||||
:metadata {:content-length (count (.toByteArray merged-pdf-stream))
|
|
||||||
:content-type "application/pdf"})
|
|
||||||
(str "http://" (:data-bucket env) ".s3-website-us-east-1.amazonaws.com/merged-checks/" uuid ".pdf")))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defmulti check-for-invoices (fn [invoices vendor-id vendors company bank-account type index invoice-amounts]
|
|
||||||
type))
|
|
||||||
|
|
||||||
(defmethod check-for-invoices "check" [invoices vendor-id vendors company bank-account type index invoice-amounts]
|
|
||||||
(let [uuid (str (UUID/randomUUID))
|
|
||||||
vendor (vendors vendor-id)
|
|
||||||
|
|
||||||
|
|
||||||
memo (str "Invoice #'s: "
|
|
||||||
(str/join ", "
|
|
||||||
(map (fn [i]
|
|
||||||
(str (:invoice-number i) "(" (invoice-amounts (:id i)) ")"))
|
|
||||||
invoices)))]
|
|
||||||
{:s3-uuid uuid
|
|
||||||
:s3-key (str "checks/" uuid ".pdf")
|
|
||||||
:s3-url (str "http://" (:data-bucket env) ".s3-website-us-east-1.amazonaws.com/checks/" uuid ".pdf")
|
|
||||||
:check-number (+ index (:check-number bank-account))
|
|
||||||
:type "check"
|
|
||||||
:bank-account-id (:id bank-account)
|
|
||||||
:amount (reduce + 0 (map (comp invoice-amounts :id) invoices))
|
|
||||||
:memo memo
|
|
||||||
:vendor-id (:id vendor)
|
|
||||||
:company-id (:id company)
|
|
||||||
:date (time/now)
|
|
||||||
:pdf-data {:vendor vendor
|
|
||||||
:paid-to (:name vendor)
|
|
||||||
:amount (reduce + 0 (map (comp invoice-amounts :id) invoices))
|
|
||||||
:check (str (+ index (:check-number bank-account)))
|
|
||||||
:memo memo
|
|
||||||
:date (date->str (local-now))
|
|
||||||
:company {:name (:name company)
|
|
||||||
:address (:address company)
|
|
||||||
:signature-file (:signature-file company)
|
|
||||||
:bank {:name (:bank-name bank-account)
|
|
||||||
:acct (:bank-code bank-account)
|
|
||||||
:routing (:routing bank-account)
|
|
||||||
:acct-number (:number bank-account)}}}
|
|
||||||
|
|
||||||
:invoices (map :id invoices)}))
|
|
||||||
|
|
||||||
(defmethod check-for-invoices "debit" [invoices vendor-id vendors company bank-account type index invoice-amounts]
|
|
||||||
(let [vendor (vendors vendor-id)]
|
|
||||||
{:type "debit"
|
|
||||||
:bank-account-id (:id bank-account)
|
|
||||||
:amount (reduce + 0 (map (comp invoice-amounts :id) invoices))
|
|
||||||
:status "cleared"
|
|
||||||
:memo "Debit"
|
|
||||||
:vendor-id (:id vendor)
|
|
||||||
:company-id (:id company)
|
|
||||||
:date (time/now)
|
|
||||||
:invoices (map :id invoices)}))
|
|
||||||
|
|
||||||
(defmethod check-for-invoices "cash" [invoices vendor-id vendors company bank-account type index invoice-amounts]
|
|
||||||
(let [vendor (vendors vendor-id)]
|
|
||||||
{:type "cash"
|
|
||||||
:bank-account-id (:id bank-account)
|
|
||||||
:amount (reduce + 0 (map (comp invoice-amounts :id) invoices))
|
|
||||||
:status "cleared"
|
|
||||||
:memo "Cash"
|
|
||||||
:vendor-id (:id vendor)
|
|
||||||
:company-id (:id company)
|
|
||||||
:date (time/now)
|
|
||||||
:invoices (map :id invoices)}))
|
|
||||||
|
|
||||||
(defn print-checks [invoice-payments company-id bank-account-id type]
|
|
||||||
(let [invoices (invoices/get-multi (map :invoice-id invoice-payments))
|
|
||||||
company (companies/get-by-id company-id)
|
|
||||||
vendors (by :id (vendors/get-all))
|
|
||||||
invoice-amounts (by :invoice-id :amount invoice-payments)
|
|
||||||
invoices-grouped-by-vendor (group-by :vendor-id invoices)
|
|
||||||
bank-account (first (filter #(= (:id %) bank-account-id) (:bank-accounts company)))
|
|
||||||
checks (-> (for [[[vendor-id invoices] index] (map vector invoices-grouped-by-vendor (range))]
|
|
||||||
[invoices (checks/insert! (check-for-invoices invoices vendor-id vendors company bank-account type index invoice-amounts))])
|
|
||||||
doall)
|
|
||||||
invoice-checks (invoices-checks/insert-multi!
|
|
||||||
(mapcat
|
|
||||||
(fn [[invoices check]]
|
|
||||||
(map
|
|
||||||
(fn [i]
|
|
||||||
{:invoice-id (:id i)
|
|
||||||
:check-id (:id check)
|
|
||||||
:amount (invoice-amounts (:id i))})
|
|
||||||
invoices))
|
|
||||||
checks))
|
|
||||||
updated-company (if (= type "check" )
|
|
||||||
(update company :bank-accounts
|
|
||||||
(fn [bas]
|
|
||||||
(map (fn [ba]
|
|
||||||
(if (= bank-account-id (:id ba))
|
|
||||||
(update ba :check-number + (count checks))
|
|
||||||
ba))
|
|
||||||
bas)))
|
|
||||||
company)]
|
|
||||||
|
|
||||||
(when (= type "check")
|
|
||||||
(make-pdfs (map second checks))
|
|
||||||
(companies/upsert company-id updated-company))
|
|
||||||
(doseq [{:keys [invoice-id amount]} invoice-payments]
|
|
||||||
(invoices/apply-payment invoice-id amount))
|
|
||||||
{:invoices (invoices/get-multi (map :id (mapcat first checks)))
|
|
||||||
:pdf-url (if (= type "check")
|
|
||||||
(merge-pdfs (map (comp :s3-key second) checks))
|
|
||||||
nil)}))
|
|
||||||
|
|
||||||
|
|
||||||
(defroutes routes
|
(defroutes routes
|
||||||
(wrap-routes
|
(wrap-routes
|
||||||
(context "/checks" [])
|
(context "/checks" [])
|
||||||
|
|||||||
@@ -149,7 +149,7 @@
|
|||||||
(assoc-in i f v)
|
(assoc-in i f v)
|
||||||
i))))))
|
i))))))
|
||||||
|
|
||||||
(defn print-checks-query [invoice-payments bank-account-id type company-id]
|
(defn print-checks-query [invoice-payments bank-account-id type client-id]
|
||||||
{:venia/operation {:operation/type :mutation
|
{:venia/operation {:operation/type :mutation
|
||||||
:operation/name "PrintChecks"}
|
:operation/name "PrintChecks"}
|
||||||
|
|
||||||
@@ -157,8 +157,8 @@
|
|||||||
{:invoice_payments invoice-payments
|
{:invoice_payments invoice-payments
|
||||||
:type type
|
:type type
|
||||||
:bank_account_id bank-account-id
|
:bank_account_id bank-account-id
|
||||||
:company_id company-id}
|
:client_id client-id}
|
||||||
[[:invoices [:id :outstanding-balance [:checks [:amount [:check [:amount :s3_url :check_number ]]]]]]
|
[[:invoices [:id :outstanding-balance [:payments [:amount [:payment [:amount :s3_url :check_number ]]]]]]
|
||||||
:pdf_url]]]})
|
:pdf_url]]]})
|
||||||
|
|
||||||
(re-frame/reg-event-fx
|
(re-frame/reg-event-fx
|
||||||
|
|||||||
Reference in New Issue
Block a user