Files
integreat/src/clj/auto_ap/routes/checks.clj
2018-07-27 16:39:44 -07:00

277 lines
12 KiB
Clojure

(ns auto-ap.routes.checks
(:require [auto-ap.db.companies :as companies]
[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]]
[auto-ap.routes.utils :refer [wrap-secure]]
[auto-ap.time :refer [local-now]]
[config.core :refer [env]]
[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 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
(- 100
(count word-amount)))
(repeat "-"))))
[:line {:line-width 0.15 :color [50 50 50]}]]
[:cell {:colspan 3}]]
[[:cell {:colspan 6 :leading 50} [:spacer]]
[:cell {:colspan 6 :rowspan 2} (if (:signature-file company)
[:image { :top-margin 90 :xscale 0.30 :yscale 0.30 :align :center}
(:signature-file company)]
[:spacer])]]
[[:cell {:size 9 } "MEMO"]
[:cell {:colspan 5} memo [:line {:line-width 0.15 :color [50 50 50]}]]
#_[: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 index invoice-amounts]
(:type bank-account)))
(defmethod check-for-invoices "check" [invoices vendor-id vendors company bank-account 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 "cash" [invoices vendor-id vendors company bank-account 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]
(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 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 bank-account) "cash" )
company
(update company :bank-accounts
(fn [bas]
(map (fn [ba]
(if (= bank-account-id (:id ba))
(update ba :check-number + (count checks))
ba))
bas))))]
(when (not= (:type bank-account) "cash")
(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 bank-account) "cash")
nil
(merge-pdfs (map (comp :s3-key second) checks)))}))
(defroutes routes
(wrap-routes
(context "/checks" [])
wrap-secure))