(ns auto-ap.db.invoices (:require [auto-ap.db.utils :refer [clj->db kebab->snake db->clj get-conn query limited-companies] :as utils] [auto-ap.parse :as parse] [auto-ap.db.companies :as companies] [auto-ap.db.invoices-checks :as invoices-checks] [auto-ap.db.vendors :as vendors] [auto-ap.entities.companies :as company] [auto-ap.entities.vendors :as vendor] [clojure.java.jdbc :as j] [clojure.string :as str] [honeysql.core :as sql] [honeysql.helpers :as helpers])) (def all-keys #{:company-id :vendor-id :imported :potential-duplicate :total :invoice-number :date :outstanding-balance :default-location :default-expense-account}) (defn insert-multi! [rows] (j/insert-multi! (get-conn) :invoices (map clj->db rows))) (defn upsert-multi! [rows] (let [k (vec (map #(keyword (kebab->snake (name %))) [:company-id :vendor-id :invoice-number :total :date :imported :status :outstanding-balance :default-location :default-expense-account])) column-names (str/join "," (map name k))] (reduce (fn [affected rows] (concat affected (let [[query & params] (sql/format {:with [[[:v (str "(" column-names ")")] (helpers/values (->> rows (map clj->db ) (map (apply juxt k))))]] :insert-into [[:invoices k] {:select [:v.company-id :v.vendor-id :v.invoice-number :v.total (sql/raw "cast(v.date as timestamp)") :v.imported :v.status :v.outstanding-balance :v.default-location (sql/raw "cast(v.default_expense_account as INT)")] :from [:v] :left-join [[:invoices :exist] [:and [:= :exist.invoice-number :v.invoice-number] [:not= :exist.status "voided"] [:= :exist.company-id :v.company-id] [:or [:= :exist.vendor-id :v.vendor-id] [:and [:= :exist.vendor-id nil] [:= :v.vendor-id nil]]]]] :where [:= :exist.id nil] }]}) statement (j/prepare-statement (j/get-connection (get-conn)) query {:return-keys true})] (j/execute! (get-conn) (concat [statement] params)) (->> (j/result-set-seq (.getGeneratedKeys statement)) (map db->clj) doall)))) [] (partition-all 2000 rows)))) (def base-query (sql/build :select :invoices.* :from :invoices)) (defn get-all [] (query base-query)) (defn find-conflicting [{:keys [id invoice-number company-id vendor-id] :as i}] (query (-> base-query (helpers/merge-where [:and [:not= :id id] [:= :invoice-number invoice-number] [:= :company-id company-id] [:= :vendor-id vendor-id] [:not= :status "voided"]])))) (defn get-multi [ids] (query (-> base-query (helpers/merge-where [:in :id ids])))) (defn get-by-id [id] (first (query (-> base-query (helpers/merge-where [:= :id id]))))) (defn approve [] (j/update! (get-conn) :invoices {:imported true} [] )) (defn update [v] (j/update! (get-conn) :invoices (clj->db v) ["id = ?" (:id v)]) (get-by-id (:id v))) (defn reject [] (j/delete! (get-conn) :invoices ["imported = false"])) (defn get-unpaid [company] (query (if company (-> base-query (helpers/merge-where [:= :imported true]) (helpers/merge-where [:= :company-id (if (int? company) company (Integer/parseInt company))])) (-> base-query (helpers/merge-where [:= :imported true]))))) (defn get-pending [company] (query (if company (-> base-query (helpers/merge-where [:= :imported false]) (helpers/merge-where [:= :company-id (if (int? company) company (Integer/parseInt company))])) (-> base-query (helpers/merge-where [:= :imported false]))))) (defn add-sort-by [q sort-by asc] (let [sort-by-key (keyword sort-by)] (cond (nil? sort-by) (helpers/merge-order-by q [:date]) (= :date sort-by-key) (helpers/merge-order-by q [:date (when-not asc :desc)]) (all-keys sort-by-key) (helpers/merge-order-by q [sort-by-key (when-not asc :desc)] [:date]) (= :vendor sort-by-key) (-> q (helpers/merge-left-join [:vendors :v] [:= :v.id :invoices.vendor-id] ) (helpers/merge-order-by [:v.name (when-not asc :desc)] [:date])) (= :company sort-by-key) (-> q (helpers/merge-left-join [:companies :c] [:= :c.id :invoices.company-id] ) (helpers/merge-order-by [:c.name (when-not asc :desc)] [:date])) :else q))) (defn base-graphql [{:keys [imported company-id status id statuses]}] (cond-> base-query (limited-companies id) (helpers/merge-where [:in :company-id (limited-companies id)]) (seq statuses ) (helpers/merge-where [:in :status statuses]) (not (nil? imported)) (helpers/merge-where [:= :imported imported]) (not (nil? status)) (helpers/merge-where [:= :status status]) (not (nil? company-id)) (helpers/merge-where [:= :company-id company-id]))) (defn get-graphql [{:keys [start sort-by asc limit] :as args :or {limit 100}}] (query (cond-> (base-graphql args) true (add-sort-by sort-by asc) true (assoc :limit limit) start (assoc :offset start)))) (defn count-graphql [args] (:count (first (query (assoc (base-graphql args) :select [:%count.*]))))) (defn import [parsed-invoices companies vendors] (->> (insert-multi! (for [{:keys [total date invoice-number customer-identifier vendor-code] :as row} parsed-invoices] (do (dissoc (assoc row :company-id (:id (parse/best-match companies customer-identifier)) :vendor-id (:id (first (filter #(= (:code %) vendor-code) vendors))) :imported false :potential-duplicate false) :vendor-code)))) (map db->clj))) (defn apply-payment [invoice-id amount] (j/db-do-prepared (get-conn) (-> (helpers/update :invoices) (helpers/sset {:outstanding-balance (sql/call :- :outstanding-balance amount)}) (helpers/where [:= :id invoice-id]) (sql/format))) (j/db-do-prepared (get-conn) (-> (helpers/update :invoices) (helpers/sset {:status "paid"}) (helpers/where [:and [:< :outstanding-balance 0.01] [:= :id invoice-id]]) (sql/format))))