<commit_before>(ns clj-money.models.transactions
  (:refer-clojure :exclude [update])
  (:require [clojure.pprint :refer [pprint]]
            [clojure.spec :as s]
            [clojure.set :refer [difference]]
            [clj-time.core :as t]
            [clj-money.util :refer [pprint-and-return]]
            [clj-money.coercion :as coercion]
            [clj-money.validation :as validation]
            [clj-money.authorization :as authorization]
            [clj-money.models.accounts :as accounts]
            [clj-money.models.helpers :refer [with-storage with-transacted-storage]]
            [clj-money.models.storage :refer [select-transactions
                                              create-transaction
                                              create-transaction-item
                                              update-transaction
                                              select-transaction-items
                                              select-lots-transactions-by-transaction-id
                                              update-transaction-item
                                              update-transaction-item-index-and-balance
                                              delete-transaction
                                              delete-transaction-item
                                              delete-transaction-items-by-transaction-id
                                              create-lot->transaction-link
                                              delete-lot->transaction-link]])
  (:import org.joda.time.LocalDate
           java.util.UUID))

(s/def ::account-id integer?)
(s/def ::action #{:debit :credit})
; Amount is the quantity of the commodity that is exchanged
(s/def ::amount validation/positive-big-dec?)
; Balance is the running total of amounts for the account to which
; the item belongs
(s/def ::balance (partial instance? BigDecimal))
; Value is the value of the line item expressed in the entity's
; default currency. For most transactions, this will be the same
; as the amount. For transactions involving foreign currencies
; and commodity purchases (like stock trades) it will be different.
(s/def ::value validation/positive-big-dec?)
(s/def ::description validation/non-empty-string?)
(s/def ::memo #(or (nil? %) (string? %)))
(s/def ::transaction-date (partial instance? LocalDate))
(s/def ::id uuid?)
(s/def ::entity-id integer?)
(s/def ::lot-id integer?)
(s/def ::lot-action #{:buy :sell})
(s/def ::shares decimal?)
(s/def ::lot-item (s/keys :req-un [::lot-id ::shares ::lot-action ::price]))
(s/def ::lot-items (s/coll-of ::lot-item))
(s/def ::index integer?)
(s/def ::transaction-item (s/keys :req-un [::account-id
                                           ::action
                                           ::amount]
                                  :opt-un [::balance
                                           ::index
                                           ::memo]))
(s/def ::items (s/coll-of ::transaction-item :min-count 2))
(s/def ::new-transaction (s/keys :req-un [::description
                                          ::transaction-date
                                          ::items
                                          ::entity-id]
                                 :opt-un [::memo
                                          ::lot-items]))
(s/def ::existing-transaction (s/keys :req-un [::id
                                               ::transaction-date
                                               ::items]
                                      :opt-un [::entity-id
                                               ::memo
                                               ::lot-items]))

(def ambient-settings
  (atom {}))

(defn- delay-balances?
  [entity-id]
  true
  (get-in @ambient-settings [entity-id :delay-balances?]))

(defn- before-save-item
  "Makes pre-save adjustments for a transaction item"
  [item]
  (cond-> item
    true (update-in [:action] name)
    (and (string? (:memo item))
         (empty? (:memo item))) (dissoc :memo)))

(defn polarize-item-amount
  [item account]
  (assoc item :polarized-amount (accounts/polarize-amount item account)))

(defn- after-item-read
  "Makes adjustments to a transaction item in prepartion for return
  from the data store"
  ([item] (after-item-read item nil))
  ([item account]
   (if (map? item)
     (cond-> item
       true (update-in [:action] keyword)
       true (assoc :reconciled? (= "completed" (:reconciliation-status item)))
       account (polarize-item-amount account))
     item)))

(defn- item-value-sum
  "Returns the sum of values of the items in the transaction having
  the specified action"
  [transaction action]
  (reduce + 0 (->> (:items transaction)

                   (filter #(= action (:action %)))
                   (map :value))))

(defn- ^{:clj-money.validation/message "The total debits does not match the total credits"
         :clj-money.validation/path [:items]}
  sum-of-credits-must-equal-sum-of-debits
  [transaction]
  (->> [:debit :credit]
       (map #(item-value-sum transaction %))
       (apply =)))

(defn- ^{:clj-money.validation/message "Each item must have the same transaction-date as the transaction"
         :clj-money.validation/path [:items]}
  transaction-dates-must-match
  [{:keys [transaction-date items] :as transaction}]
  (->> items
       (map :transaction-date)
       (apply = transaction-date)))

(defn- before-item-validation
  [item]
  (cond-> item
    true (update-in [:value] #(or % (:amount item)))
    true (assoc :balance (bigdec 0))
    (string? (:account-id item)) (update-in [:account-id] #(Integer. %))
    (nil? (:id item)) (dissoc :id)
    (and
      (string? (:id item))
      (empty? (:id item))) (dissoc :id)
    (and
      (string? (:id item))
      (not (empty? (:id item)))) (update-in [:id] #(Integer. %))))

(def ^:private coercion-rules
  [(coercion/rule :uuid [:id])
   (coercion/rule :integer [:entity-id])
   (coercion/rule :local-date [:transaction-date])])

(defn- before-validation
  "Performs operations required before validation"
  [transaction]
  (-> (coercion/coerce coercion-rules transaction)
      (update-in [:items] (fn [items]
                            (->> items
                                 (map #(assoc % :transaction-date
                                                (:transaction-date transaction)))
                                 (map before-item-validation))))))
<commit_msg>ensure dates are compared to dates<commit_after>(ns clj-money.models.transactions
  (:refer-clojure :exclude [update])
  (:require [clojure.pprint :refer [pprint]]
            [clojure.spec :as s]
            [clojure.set :refer [difference]]
            [clj-time.core :as t]
            [clj-money.util :refer [pprint-and-return
                                    ensure-local-date]]
            [clj-money.coercion :as coercion]
            [clj-money.validation :as validation]
            [clj-money.authorization :as authorization]
            [clj-money.models.accounts :as accounts]
            [clj-money.models.helpers :refer [with-storage with-transacted-storage]]
            [clj-money.models.storage :refer [select-transactions
                                              create-transaction
                                              create-transaction-item
                                              update-transaction
                                              select-transaction-items
                                              select-lots-transactions-by-transaction-id
                                              update-transaction-item
                                              update-transaction-item-index-and-balance
                                              delete-transaction
                                              delete-transaction-item
                                              delete-transaction-items-by-transaction-id
                                              create-lot->transaction-link
                                              delete-lot->transaction-link]])
  (:import org.joda.time.LocalDate
           java.util.UUID))

(s/def ::account-id integer?)
(s/def ::action #{:debit :credit})
; Amount is the quantity of the commodity that is exchanged
(s/def ::amount validation/positive-big-dec?)
; Balance is the running total of amounts for the account to which
; the item belongs
(s/def ::balance (partial instance? BigDecimal))
; Value is the value of the line item expressed in the entity's
; default currency. For most transactions, this will be the same
; as the amount. For transactions involving foreign currencies
; and commodity purchases (like stock trades) it will be different.
(s/def ::value validation/positive-big-dec?)
(s/def ::description validation/non-empty-string?)
(s/def ::memo #(or (nil? %) (string? %)))
(s/def ::transaction-date (partial instance? LocalDate))
(s/def ::id uuid?)
(s/def ::entity-id integer?)
(s/def ::lot-id integer?)
(s/def ::lot-action #{:buy :sell})
(s/def ::shares decimal?)
(s/def ::lot-item (s/keys :req-un [::lot-id ::shares ::lot-action ::price]))
(s/def ::lot-items (s/coll-of ::lot-item))
(s/def ::index integer?)
(s/def ::transaction-item (s/keys :req-un [::account-id
                                           ::action
                                           ::amount]
                                  :opt-un [::balance
                                           ::index
                                           ::memo]))
(s/def ::items (s/coll-of ::transaction-item :min-count 2))
(s/def ::new-transaction (s/keys :req-un [::description
                                          ::transaction-date
                                          ::items
                                          ::entity-id]
                                 :opt-un [::memo
                                          ::lot-items]))
(s/def ::existing-transaction (s/keys :req-un [::id
                                               ::transaction-date
                                               ::items]
                                      :opt-un [::entity-id
                                               ::memo
                                               ::lot-items]))

(def ambient-settings
  (atom {}))

(defn- delay-balances?
  [entity-id]
  true
  (get-in @ambient-settings [entity-id :delay-balances?]))

(defn- before-save-item
  "Makes pre-save adjustments for a transaction item"
  [item]
  (cond-> item
    true (update-in [:action] name)
    (and (string? (:memo item))
         (empty? (:memo item))) (dissoc :memo)))

(defn polarize-item-amount
  [item account]
  (assoc item :polarized-amount (accounts/polarize-amount item account)))

(defn- after-item-read
  "Makes adjustments to a transaction item in prepartion for return
  from the data store"
  ([item] (after-item-read item nil))
  ([item account]
   (if (map? item)
     (cond-> item
       true (update-in [:action] keyword)
       true (assoc :reconciled? (= "completed" (:reconciliation-status item)))
       account (polarize-item-amount account))
     item)))

(defn- item-value-sum
  "Returns the sum of values of the items in the transaction having
  the specified action"
  [transaction action]
  (reduce + 0 (->> (:items transaction)

                   (filter #(= action (:action %)))
                   (map :value))))

(defn- ^{:clj-money.validation/message "The total debits does not match the total credits"
         :clj-money.validation/path [:items]}
  sum-of-credits-must-equal-sum-of-debits
  [transaction]
  (->> [:debit :credit]
       (map #(item-value-sum transaction %))
       (apply =)))

(defn- ^{:clj-money.validation/message "Each item must have the same transaction-date as the transaction"
         :clj-money.validation/path [:items]}
  transaction-dates-must-match
  [{:keys [transaction-date items] :as transaction}]
  (->> items
       (map (comp ensure-local-date :transaction-date))
       (apply = transaction-date)))

(defn- before-item-validation
  [item]
  (cond-> item
    true (update-in [:value] #(or % (:amount item)))
    true (assoc :balance (bigdec 0))
    (string? (:account-id item)) (update-in [:account-id] #(Integer. %))
    (nil? (:id item)) (dissoc :id)
    (and
      (string? (:id item))
      (empty? (:id item))) (dissoc :id)
    (and
      (string? (:id item))
      (not (empty? (:id item)))) (update-in [:id] #(Integer. %))))

(def ^:private coercion-rules
  [(coercion/rule :uuid [:id])
   (coercion/rule :integer [:entity-id])
   (coercion/rule :local-date [:transaction-date])])

(defn- before-validation
  "Performs operations required before validation"
  [transaction]
  (-> (coercion/coerce coercion-rules transaction)
      (update-in [:items] (fn [items]
                            (->> items
                                 (map #(assoc % :transaction-date
                                                (:transaction-date transaction)))
                                 (map before-item-validation))))))

{'fraction_non_alphanumeric': 0.12936313533374158, 'fraction_numerical': 0.00045927740355174526, 'mean_word_length': 1.6377952755905512, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 111, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '114334', 'n_tokens_mistral': 3554, 'n_tokens_neox': 3414, 'n_words': 936}