Less error prone

This commit is contained in:
2022-01-19 08:05:43 -08:00
parent cd5b1cbb8a
commit 6e0720c3b3
8 changed files with 284 additions and 308 deletions

View File

@@ -67,8 +67,8 @@
(header-row "City2")])
account-number (some-> account-number Long/parseLong str)
matching-client (and account-number
(parse/best-match clients account-number 0.0))
[matching-client similarity] (and account-number
(parse/best-match clients account-number 0.0))
_ (when-not matching-client
(throw (ex-info "cannot find matching client"
{:account-number account-number
@@ -80,16 +80,18 @@
"yyMMdd")]
(log/infof "Importing %s for %s" (header-row "InvoiceNumber") (header-row "CustomerName"))
(code-invoice #:invoice {:invoice-number (header-row "InvoiceNumber")
:total (+ total tax)
:outstanding-balance (+ total tax)
:location (parse/best-location-match matching-client location-hint location-hint )
:date (coerce/to-date date)
:vendor (:db/id sysco-vendor )
:client (:db/id matching-client)
:import-status :import-status/completed
:status :invoice-status/unpaid
:client-identifier customer-identifier})))
(cond-> #:invoice {:invoice-number (header-row "InvoiceNumber")
:total (+ total tax)
:outstanding-balance (+ total tax)
:location (parse/best-location-match matching-client location-hint location-hint )
:date (coerce/to-date date)
:vendor (:db/id sysco-vendor )
:client (:db/id matching-client)
:import-status :import-status/completed
:status :invoice-status/unpaid
:client-identifier customer-identifier}
similarity (assoc :invoice/similarity (- 1.0 (double similarity)))
true (code-invoice))))
(defn mark-key [k]
(s3/copy-object {:source-bucket-name bucket-name

View File

@@ -479,7 +479,11 @@
{:db/ident :invoice/location
:db/doc "The location to code the invoice as"
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one}]]}}
:db/cardinality :db.cardinality/one}]]}
:auto-ap/add-invoice-similarity {:txes [[{:db/ident :invoice/similarity
:db/doc "How close an invoice matches its import"
:db/valueType :db.type/double
:db/cardinality :db.cardinality/one}]]}}

View File

@@ -127,7 +127,7 @@
:detailed_status {:type 'String}
:last_updated {:type :iso_date}
:accounts {:type '(list :yodlee_account)}}}
:yodlee_account
{:fields {:id {:type 'Int}
:status {:type 'String}
@@ -198,7 +198,6 @@
{:fields {:id {:type :id}
:client {:type :client}
:terms {:type 'Int}}}
:schedule_payment_dom
{:fields {:id {:type :id}
@@ -240,7 +239,7 @@
:legal_entity_tin_type {:type :tin_type}
:legal_entity_1099_type {:type :type_1099}}}
:reminder
{:fields {:id {:type 'Int}
:email {:type 'String}
@@ -249,7 +248,6 @@
:scheduled {:type 'String}
:sent {:type 'String}
:vendor {:type :vendor}}}
:journal_entry_line
{:fields {:id {:type :id}
@@ -302,8 +300,6 @@
:charges {:type '(list :charge)}
:line_items {:type '(list :order_line_item)}}}
:check {:fields {:id {:type :id}
:type {:type 'String}
:amount {:type 'String}
@@ -315,7 +311,6 @@
:check_number {:type 'Int}
:status {:type 'String}
:invoices {:type '(list :invoice_payment)}}}
:payment {:fields {:id {:type :id}
:type {:type :payment_type}
@@ -331,7 +326,6 @@
:status {:type :ident}
:transaction {:type :transaction}
:invoices {:type '(list :invoice_payment)}}}
:yodlee_merchant {:fields {:id {:type :id}
:yodlee_id {:type 'String}
@@ -343,7 +337,6 @@
:forecast_match {:fields {:id {:type :id}
:identifier {:type 'String}}}
:transaction_rule {:fields {:id {:type :id}
:note {:type 'String}
@@ -387,60 +380,24 @@
:name {:type 'String}
:client_overrides {:type '(list :account_client_override)}}}
:invoices_expense_accounts
{:fields {:id {:type :id}
:invoice_id {:type 'String}
:account {:type :account}
:location {:type 'String}
:amount {:type 'String}}}
:percentage_account
{:fields {:id {:type :id}
:account {:type :account}
:location {:type 'String}
:percentage {:type :percentage}}}
:invoice
{:fields {:id {:type :id}
:original_id {:type 'Int}
:client_identifier {:type 'String}
:total {:type 'String}
:source_url {:type 'String}
:outstanding_balance {:type 'String}
:invoice_number {:type 'String}
:status {:type 'String}
:expense_accounts {:type '(list :invoices_expense_accounts)}
:date {:type :iso_date}
:due {:type :iso_date}
:client_id {:type 'Int}
:payments {:type '(list :invoice_payment)}
:vendor {:type :vendor}
:client {:type :client}
:scheduled_payment {:type :iso_date}}}
:yodlee_provider_account_page {:fields {:yodlee_provider_accounts {:type '(list :yodlee_provider_account)}
:count {:type 'Int}
:total {:type 'Int}
:start {:type 'Int}
:end {:type 'Int}}}
:invoice_page {:fields {:invoices {:type '(list :invoice)}
:outstanding {:type :money}
:count {:type 'Int}
:total {:type 'Int}
:start {:type 'Int}
:end {:type 'Int}}}
:payment_page {:fields {:payments {:type '(list :payment)}
:count {:type 'Int}
:total {:type 'Int}
:start {:type 'Int}
:end {:type 'Int}}}
:transaction_rule_page {:fields {:transaction_rules {:type '(list :transaction_rule)}
:count {:type 'Int}
:total {:type 'Int}
@@ -452,7 +409,7 @@
:total {:type 'Int}
:start {:type 'Int}
:end {:type 'Int}}}
:sales_order_page {:fields {:sales_orders {:type '(list :sales_order)}
:count {:type 'Int}
:total {:type 'Int}
@@ -460,10 +417,6 @@
:end {:type 'Int}
:sales_order_total {:type :money}
:sales_order_tax {:type :money}}}
:reminder_page {:fields {:reminders {:type '(list :reminder)}
:count {:type 'Int}
@@ -488,7 +441,6 @@
:existing {:type '(list :import_ledger_entry_result)}
:ignored {:type '(list :import_ledger_entry_result)}
:errors {:type '(list :import_ledger_entry_result)}}}
:upcoming_transaction {:fields {:amount {:type :money}
:identifier {:type 'String}
@@ -499,9 +451,6 @@
:outstanding_payments {:type :money}
:upcoming_credits {:type '(list :upcoming_transaction)}
:upcoming_debits {:type '(list :upcoming_transaction)}}}}
:queries
{:expense_account_stats {:type '(list :expense_account_stat)
@@ -542,38 +491,10 @@
:periods {:type '(list :date_range)}}
:resolve :get-profit-and-loss}
:yodlee_provider_account_page {:type :yodlee_provider_account_page
:args {:client_id {:type :id}}
:resolve :get-yodlee-provider-account-page}
:invoice_page {:type '(list :invoice_page)
:args {:import_status {:type 'String}
:exact_match_id {:type :id}
:date_range {:type :date_range}
:due_range {:type :date_range}
:status {:type :invoice_status}
:unresolved {:type 'Boolean}
:scheduled_payments {:type 'Boolean}
:client_id {:type :id}
:vendor_id {:type :id}
:amount_lte {:type :money}
:amount_gte {:type :money}
:invoice_number_like {:type 'String}
:location {:type 'String}
:start {:type 'Int}
:per_page {:type 'Int}
:sort {:type '(list :sort_item)}}
:resolve :get-invoice-page}
:all_invoices {:type '(list :invoice)
:args {:client_id {:type :id}
:client_code {:type 'String}
:original_id {:type 'Int}
:statuses {:type '(list String)}}
:resolve :get-all-invoices}
:accounts {:type '(list :account)
:args {:account_set {:type 'String}}
:resolve :get-accounts}
@@ -585,8 +506,6 @@
:statuses {:type '(list String)}}
:resolve :get-all-payments}
:all_sales_orders {:type '(list :sales_order)
:args {:client_id {:type :id}
:date_range {:type :date_range}
@@ -596,15 +515,11 @@
:yodlee_merchants {:type '(list :yodlee_merchant)
:args {}
:resolve :get-yodlee-merchants}
:intuit_bank_accounts {:type '(list :intuit_bank_account)
:args {}
:resolve :get-intuit-bank-accounts}
:transaction_rule_page {:type :transaction_rule_page
:args {:client_id {:type :id}
:vendor_id {:type :id}
@@ -629,11 +544,8 @@
:start {:type 'Int}
:per_page {:type 'Int}
:sort {:type '(list :sort_item)}}
:resolve :get-sales-order-page}
:payment_page {:type '(list :payment_page)
:args {:client_id {:type :id}
:vendor_id {:type :id}
@@ -647,9 +559,8 @@
:start {:type 'Int}
:per_page {:type 'Int}
:sort {:type '(list :sort_item)}}
:resolve :get-payment-page}
:client {:type '(list :client)
:resolve :get-client}
:vendor {:type '(list :vendor)
@@ -657,8 +568,6 @@
:user {:type '(list :user)
:resolve :get-user}}
:input-objects
{
:sort_item
@@ -666,9 +575,6 @@
:sort_name {:type 'String}
:asc {:type 'Boolean}}}
:ledger_filters {:fields {:client_id {:type :id}
:vendor_id {:type :id}
:account_id {:type :id}
@@ -728,7 +634,7 @@
:location_matches {:type '(list :edit_location_match)}
:bank_accounts {:type '(list :edit_bank_account)}
:forecasted_transactions {:type '(list :edit_forecasted_transaction)}}}
:edit_bank_account
{:fields {:id {:type :id}
:code {:type 'String}
@@ -771,7 +677,6 @@
{:fields {:id {:type :id}
:client_id {:type :id}
:terms {:type 'Int}}}
:add_account_override
{:fields {:id {:type :id}
@@ -790,54 +695,21 @@
:terms_overrides {:type '(list :add_terms_override)}
:code {:type 'String}
:automatically_paid_when_due {:type '(list :id)}
:hidden {:type 'Boolean}
:print_as {:type 'String}
:primary_contact {:type :add_contact}
:secondary_contact {:type :add_contact}
:address {:type :add_address}
:default_account_id {:type :id}
:account_overrides {:type '(list :add_account_override)}
:schedule_payment_dom {:type '(list :add_schedule_payment_dom)}
:invoice_reminder_schedule {:type 'String}
:legal_entity_first_name {:type 'String}
:legal_entity_middle_name {:type 'String}
:legal_entity_last_name {:type 'String}
:legal_entity_tin {:type 'String}
:legal_entity_tin_type {:type :tin_type}
:legal_entity_1099_type {:type :type_1099}}}
:edit_expense_account
{:fields {:id {:type :id}
:account_id {:type :id}
:location {:type 'String}
:amount {:type :money}}}
:add_invoice
{:fields {:id {:type :id}
:invoice_number {:type 'String}
:expense_accounts {:type '(list :edit_expense_account)}
:location {:type 'String}
:scheduled_payment {:type :iso_date}
:date {:type :iso_date}
:due {:type :iso_date}
:client_id {:type :id}
:vendor_id {:type :id}
:vendor_name {:type 'String}
:total {:type :money}}}
:edit_invoice
{:fields {:id {:type :id}
:invoice_number {:type 'String}
:expense_accounts {:type '(list :edit_expense_account)}
:date {:type :iso_date}
:scheduled_payment {:type :iso_date}
:due {:type :iso_date}
:total {:type :money}}}
:edit_percentage_account
{:fields {:id {:type :id}
@@ -888,9 +760,6 @@
:type_1099 {:values [{:enum-value :none}
{:enum-value :misc}
{:enum-value :landlord}]}
:invoice_status {:values [{:enum-value :paid}
{:enum-value :unpaid}
{:enum-value :voided}]}
:bank_account_type {:values [{:enum-value :check}
{:enum-value :credit}
{:enum-value :cash}]}
@@ -902,28 +771,17 @@
{:enum-value :asset}
{:enum-value :liability}
{:enum-value :equity}
{:enum-value :revenue}]}
}
{:enum-value :revenue}]}}
:mutations
{:request_import {:type 'String
:args {:which {:type 'String}}
:resolve :mutation/request-import}
:reject_invoices {:type '(list :id)
:args {:invoices {:type '(list :id)}}
:resolve :mutation/reject-invoices}
:approve_invoices {:type '(list :id)
:args {:invoices {:type '(list :id)}}
:resolve :mutation/approve-invoices}
:delete_external_ledger {:type :message
:args {:filters {:type :ledger_filters}
:ids {:type '(list :id)}}
:resolve :mutation/delete-external-ledger}
:delete_transaction_rule {:type :id
:args {:transaction_rule_id {:type :id}}
:resolve :mutation/delete-transaction-rule}
@@ -932,12 +790,6 @@
:to {:type :id}}
:resolve :mutation/merge-vendors}
:add_and_print_invoice {:type :check_result
:args {:invoice {:type :add_invoice}
:bank_account_id {:type :id}
:type {:type :payment_type}}
:resolve :mutation/add-and-print-invoice}
:print_checks {:type :check_result
:args {:invoice_payments {:type '(list :invoice_payment_amount)}
:bank_account_id {:type :id}
@@ -966,38 +818,18 @@
:upsert_transaction_rule {:type :transaction_rule
:args {:transaction_rule {:type :edit_transaction_rule}}
:resolve :mutation/upsert-transaction-rule}
:add_invoice {:type :invoice
:args {:invoice {:type :add_invoice}}
:resolve :mutation/add-invoice}
:import_ledger {:type :import_ledger_result
:args {:entries {:type '(list :import_ledger_entry)}}
:resolve :mutation/import-ledger}
:edit_invoice {:type :invoice
:args {:invoice {:type :edit_invoice}}
:resolve :mutation/edit-invoice}
:upsert_account {:type :account
:args {:account {:type :edit_account}}
:resolve :mutation/upsert-account}
:void_invoice {:type :invoice
:args {:invoice_id {:type :id}}
:resolve :mutation/void-invoice}
:unvoid_invoice {:type :invoice
:args {:invoice_id {:type :id}}
:resolve :mutation/unvoid-invoice}
:unautopay_invoice {:type :invoice
:args {:invoice_id {:type :id}}
:resolve :mutation/unautopay-invoice}
:void_payment {:type :payment
:args {:payment_id {:type :id}}
:resolve :mutation/void-payment}
:edit_expense_accounts {:type :invoice
:args {:invoice_id {:type :id}
:expense_accounts {:type '(list :edit_expense_account)}}
:resolve :mutation/edit-expense-accounts}}})
:resolve :mutation/void-payment}}})
(defn snake->kebab [s]
@@ -1226,8 +1058,7 @@
(def schema
(-> integreat-schema
(attach-resolvers {:get-invoice-page gq-invoices/get-invoice-page
:get-all-invoices gq-invoices/get-all-invoices
(attach-resolvers {:mutation/void-payment gq-checks/void-check
:get-yodlee-provider-account-page gq-yodlee2/get-yodlee-provider-account-page
:get-all-payments get-all-payments
:get-all-sales-orders get-all-sales-orders
@@ -1250,12 +1081,7 @@
:mutation/add-handwritten-check gq-checks/add-handwritten-check
:mutation/delete-transaction-rule gq-transaction-rules/delete-transaction-rule
:mutation/print-checks print-checks
:mutation/reject-invoices gq-invoices/reject-invoices
:mutation/approve-invoices gq-invoices/approve-invoices
:mutation/edit-user gq-users/edit-user
:mutation/add-invoice gq-invoices/add-invoice
:mutation/add-and-print-invoice gq-invoices/add-and-print-invoice
:mutation/edit-invoice gq-invoices/edit-invoice
:mutation/delete-external-ledger gq-ledger/delete-external-ledger
:mutation/upsert-transaction-rule gq-transaction-rules/upsert-transaction-rule
:test-transaction-rule gq-transaction-rules/test-transaction-rule
@@ -1264,11 +1090,6 @@
:mutation/upsert-vendor gq-vendors/upsert-vendor
:mutation/upsert-account gq-accounts/upsert-account
:mutation/merge-vendors gq-vendors/merge-vendors
:mutation/void-invoice gq-invoices/void-invoice
:mutation/unvoid-invoice gq-invoices/unvoid-invoice
:mutation/unautopay-invoice gq-invoices/unautopay-invoice
:mutation/void-payment gq-checks/void-check
:mutation/edit-expense-accounts gq-invoices/edit-expense-accounts
:mutation/import-ledger gq-ledger/import-ledger
:mutation/request-import gq-requests/request-import
:get-vendor gq-vendors/get-graphql})
@@ -1276,6 +1097,7 @@
gq-import-batches/attach
gq-transactions/attach
gq-expected-deposit/attach
gq-invoices/attach
schema/compile))

View File

@@ -1,28 +1,33 @@
(ns auto-ap.graphql.invoices
(:require [auto-ap.graphql.utils :refer [->graphql <-graphql assert-can-see-client assert-admin assert-power-user enum->keyword]]
[auto-ap.datomic.vendors :as d-vendors]
[auto-ap.datomic.clients :as d-clients]
[auto-ap.datomic.invoices :as d-invoices]
[auto-ap.datomic.accounts :as d-accounts]
[auto-ap.graphql.checks :as gq-checks]
[auto-ap.time :refer [parse iso-date]]
[auto-ap.utils :refer [dollars=]]
[datomic.api :as d]
[auto-ap.datomic :refer [uri remove-nils audit-transact conn]]
[clj-time.coerce :as coerce]
[clj-time.core :as time]
[clojure.set :as set]
[clojure.tools.logging :as log]))
(:require
[auto-ap.datomic :refer [audit-transact conn remove-nils uri]]
[auto-ap.datomic.clients :as d-clients]
[auto-ap.datomic.invoices :as d-invoices]
[auto-ap.datomic.vendors :as d-vendors]
[auto-ap.graphql.checks :as gq-checks]
[auto-ap.graphql.utils
:refer [->graphql
<-graphql
assert-admin
assert-can-see-client
assert-power-user
enum->keyword]]
[auto-ap.utils :refer [dollars=]]
[clj-time.coerce :as coerce]
[clj-time.core :as time]
[clojure.set :as set]
[clojure.tools.logging :as log]
[com.walmartlabs.lacinia.util :refer [attach-resolvers]]
[datomic.api :as d]))
(defn get-invoice-page [context args _]
(defn get-invoice-page [context args value]
(let [args (assoc args :id (:id context))
[invoices invoice-count outstanding] (-> args
(assoc :id (:id context))
(<-graphql )
(<-graphql)
(update :status enum->keyword "invoice-status")
(d-invoices/get-graphql ))]
(d-invoices/get-graphql))]
[{:invoices (map ->graphql invoices)
:outstanding outstanding
:total invoice-count
@@ -30,44 +35,42 @@
:start (:start args 0)
:end (+ (:start args 0) (count invoices))}]))
(defn get-all-invoices [context args value]
(defn get-all-invoices [context args _]
(assert-admin (:id context))
(map
->graphql
(first (d-invoices/get-graphql (assoc (<-graphql args)
:count Integer/MAX_VALUE)))))
(defn reject-invoices [context {:keys [invoices] :as in} value]
(defn reject-invoices [context {:keys [invoices]} _]
(assert-power-user (:id context))
(doseq [i invoices]
(assert-can-see-client (:id context) (:db/id (:invoice/client (d/entity (d/db conn) i)))))
(let [transactions (map (fn [i] [:db/retractEntity i ]) invoices)
transaction-result (audit-transact transactions (:id context))]
(let [transactions (map (fn [i] [:db/retractEntity i]) invoices)]
(audit-transact transactions (:id context))
invoices))
(defn approve-invoices [context {:keys [invoices] :as in} value]
(defn approve-invoices [context {:keys [invoices]} _]
(assert-power-user (:id context))
(doseq [i invoices]
(assert-can-see-client (:id context) (:db/id (:invoice/client (d/entity (d/db conn) i)))))
(let [transactions (map (fn [i] {:db/id i :invoice/import-status :import-status/imported}) invoices)
transaction-result (audit-transact transactions (:id context))]
(let [transactions (map (fn [i] {:db/id i :invoice/import-status :import-status/imported}) invoices)]
(audit-transact transactions (:id context))
invoices))
(defn assert-no-conflicting [{:keys [total invoice_number location client_id vendor_id vendor_name date] :as in}]
(defn assert-no-conflicting [{:keys [invoice_number client_id vendor_id]}]
(when (seq (d-invoices/find-conflicting {:invoice/invoice-number invoice_number
:invoice/vendor vendor_id
:invoice/client client_id}))
(throw (ex-info (str "Invoice '" invoice_number "' already exists.") {:invoice-number invoice_number :validation-error (str "Invoice '" invoice_number "' already exists.")}))))
(defn expense-account->entity [{:keys [id account_id amount location]}]
(remove-nils #:invoice-expense-account {:amount amount
:db/id id
:db/id id
:account account_id
:location location}))
(defn add-invoice-transaction [{:keys [total invoice_number location scheduled_payment client_id vendor_id vendor_name date due expense_accounts] :as in}]
(defn add-invoice-transaction [{:keys [total invoice_number scheduled_payment client_id vendor_id date due expense_accounts]}]
(let [vendor (d-vendors/get-by-id vendor_id)
account (:vendor/default-account vendor)
due (or (and (:vendor/terms vendor)
@@ -78,24 +81,22 @@
(and (d-vendors/automatically-paid-for-client-id? vendor client_id)
due))
_ (when-not (:db/id account)
(throw (ex-info (str "Vendor '" (:vendor/name vendor) "' does not have a default expense acount.") {:vendor-id vendor_id} )))]
(cond->
{:db/id "invoice"
:invoice/invoice-number invoice_number
:invoice/client client_id
:invoice/vendor vendor_id
:invoice/import-status :import-status/imported
:invoice/total total
:invoice/outstanding-balance total
:invoice/status :invoice-status/unpaid
:invoice/date (coerce/to-date date)
:invoice/expense-accounts (map expense-account->entity
expense_accounts)}
(throw (ex-info (str "Vendor '" (:vendor/name vendor) "' does not have a default expense acount.") {:vendor-id vendor_id})))]
(cond->
{:db/id "invoice"
:invoice/invoice-number invoice_number
:invoice/client client_id
:invoice/vendor vendor_id
:invoice/import-status :import-status/imported
:invoice/total total
:invoice/outstanding-balance total
:invoice/status :invoice-status/unpaid
:invoice/date (coerce/to-date date)
:invoice/expense-accounts (map expense-account->entity
expense_accounts)}
due (assoc :invoice/due (coerce/to-date due))
scheduled_payment (assoc :invoice/scheduled-payment (coerce/to-date scheduled_payment)))))
(defn deleted-expense-accounts [invoice expense-accounts]
(let [current-expense-accounts (:invoice/expense-accounts invoice)
specified-ids (->> expense-accounts
@@ -109,16 +110,16 @@
(defn assert-valid-expense-accounts [expense_accounts]
(doseq [expense-account expense_accounts
:let [account (d/entity (d/db conn) (:account_id expense-account))]]
(log/info "ACCOUNT" (:account/location account) )
(log/info "ACCOUNT" (:account/location account))
(when (empty? (:location expense-account))
(throw (ex-info "Expense account is missing location" {:validation-error "Expense account is missing location"})))
(when (and (not (empty? (:account/location account)))
(when (and (seq (:account/location account))
(not= (:location expense-account)
(:account/location account)))
(let [err (str "Account uses location '" (:location expense-account) "' but expects '" (:account/location account) "'")]
(throw (ex-info err
{:validation-error err}))))
{:validation-error err}))))
(when (and (empty? (:account/location account))
(= "A" (:location expense-account)))
@@ -126,36 +127,33 @@
(throw (ex-info err
{:validation-error err}))))
(when (nil? (:account_id expense-account))
(throw (ex-info "Expense account is missing account" {:validation-error "Expense account is missing account"})))
(when (nil? (:amount expense-account))
(throw (ex-info "Expense account does not have an amount specified." {:validation-error "Expense account does not have an amount specified."})))))
(defn assert-invoice-amounts-add-up [{:keys [expense_accounts total]}]
(let [expense-account-total (reduce + 0 (map (fn [x] (:amount x)) expense_accounts))]
(when-not (dollars= total expense-account-total)
(when-not (dollars= total expense-account-total)
(let [error (str "Expense account total (" expense-account-total ") does not equal invoice total (" total ")")]
(throw (ex-info error {:validation-error error}))))))
(defn add-invoice [context {{:keys [total expense_accounts invoice_number location client_id vendor_id vendor_name date] :as in} :invoice} value]
(defn add-invoice [context {{:keys [expense_accounts client_id] :as in} :invoice} _]
(assert-no-conflicting in)
(assert-can-see-client (:id context) client_id)
(assert-valid-expense-accounts expense_accounts)
(assert-invoice-amounts-add-up in)
(let [transaction-result (audit-transact [(add-invoice-transaction in)] (:id context))]
(-> (d-invoices/get-by-id (get-in transaction-result [:tempids "invoice"]))
(->graphql))))
(defn assert-bank-account-belongs [client-id bank-account-id]
(when-not ((set (map :db/id (:client/bank-accounts (d-clients/get-by-id client-id)))) bank-account-id)
(throw (ex-info (str "Bank account does not belong to client") {:validation-error "Bank account does not belong to client."} ))))
(throw (ex-info (str "Bank account does not belong to client") {:validation-error "Bank account does not belong to client."}))))
(defn add-and-print-invoice [context {{:keys [total invoice_number location client_id vendor_id vendor_name date] :as in} :invoice bank-account-id :bank_account_id type :type} value]
(defn add-and-print-invoice [context {{:keys [total client_id] :as in} :invoice bank-account-id :bank_account_id type :type} _]
(assert-no-conflicting in)
(assert-can-see-client (:id context) client_id)
(assert-bank-account-belongs client_id bank-account-id)
@@ -170,8 +168,7 @@
(:id context))
->graphql)))
(defn edit-invoice [context {{:keys [id due invoice_number total vendor_id date client_id expense_accounts scheduled_payment] :as in} :invoice} value]
(defn edit-invoice [context {{:keys [id due invoice_number total date expense_accounts scheduled_payment] :as in} :invoice} _]
(let [invoice (d-invoices/get-by-id id)
_ (when (seq (d-invoices/find-conflicting {:db/id id
:invoice/invoice-number invoice_number
@@ -179,20 +176,17 @@
:invoice/client (:db/id (:invoice/client invoice))}))
(throw (ex-info (str "Invoice '" invoice_number "' already exists.") {:invoice-number invoice_number})))
paid-amount (- (:invoice/total invoice) (:invoice/outstanding-balance invoice))
_ (assert-can-see-client (:id context) (:db/id (:invoice/client invoice)))
deleted (deleted-expense-accounts invoice expense_accounts)
_ (assert-valid-expense-accounts expense_accounts)
_ (assert-invoice-amounts-add-up in)
updated-invoice (cond-> {:db/id id
:invoice/invoice-number invoice_number
:invoice/date (coerce/to-date date)
:invoice/total total
:invoice/total total
:invoice/outstanding-balance (- total paid-amount)
:invoice/expense-accounts (map expense-account->entity
expense_accounts)}
@@ -204,22 +198,21 @@
(-> (d-invoices/get-by-id id)
(->graphql))))
(defn void-invoice [context {id :invoice_id} value]
(let [invoice (d-invoices/get-by-id id)
_ (assert-can-see-client (:id context) (:db/id (:invoice/client invoice)))]
(audit-transact [{:db/id id
:invoice/total 0.0
:invoice/outstanding-balance 0.0
:invoice/status :invoice-status/voided
:invoice/expense-accounts (map (fn [ea] {:db/id (:db/id ea)
:invoice-expense-account/amount 0.0})
(:invoice/expense-accounts invoice))}]
(:id context))
(defn void-invoice [context {id :invoice_id} _]
(let [invoice (d-invoices/get-by-id id)
_ (assert-can-see-client (:id context) (:db/id (:invoice/client invoice)))]
(audit-transact [{:db/id id
:invoice/total 0.0
:invoice/outstanding-balance 0.0
:invoice/status :invoice-status/voided
:invoice/expense-accounts (map (fn [ea] {:db/id (:db/id ea)
:invoice-expense-account/amount 0.0})
(:invoice/expense-accounts invoice))}]
(:id context))
(-> (d-invoices/get-by-id id) (->graphql))))
(-> (d-invoices/get-by-id id) (->graphql))))
(defn unvoid-invoice [context {id :invoice_id} value]
(defn unvoid-invoice [context {id :invoice_id} _]
(let [invoice (d-invoices/get-by-id id)
_ (assert-can-see-client (:id context) (:db/id (:invoice/client invoice)))
conn (d/connect uri)
@@ -236,20 +229,19 @@
(audit-transact [(->> txs
(filter (fn [[tx]] (= tx last-transaction)))
(reduce (fn [new-transaction [_ entity original-status original-outstanding total expense-account expense-account-amount]]
(-> new-transaction
(-> new-transaction
(assoc :db/id entity
:invoice/total total
:invoice/status original-status
:invoice/outstanding-balance original-outstanding)
(update :invoice/expense-accounts conj {:db/id expense-account :invoice-expense-account/amount expense-account-amount}))
) {}))]
(update :invoice/expense-accounts conj {:db/id expense-account :invoice-expense-account/amount expense-account-amount}))) {}))]
(:id context))
(-> (d-invoices/get-by-id id)
(->graphql))))
(defn unautopay-invoice [context {id :invoice_id} value]
(defn unautopay-invoice [context {id :invoice_id} _]
(let [invoice (d/entity (d/db conn) id)
_ (assert (:invoice/client invoice))
_ (assert-can-see-client (:id context) (:db/id (:invoice/client invoice)))]
@@ -261,7 +253,7 @@
(-> (d-invoices/get-by-id id)
(->graphql))))
(defn edit-expense-accounts [context args value]
(defn edit-expense-accounts [context args _]
(assert-can-see-client (:id context) (:db/id (:invoice/client (d-invoices/get-by-id (:invoice_id args)))))
(let [invoice-id (:invoice_id args)
invoice (d-invoices/get-by-id invoice-id)
@@ -273,8 +265,157 @@
(:expense_accounts args))}]
(audit-transact (concat [updated]
(map (fn [d] [:db/retract invoice-id :invoice/expense-accounts d])deleted))
(map (fn [d] [:db/retract invoice-id :invoice/expense-accounts d]) deleted))
(:id context))
(->graphql
(d-invoices/get-by-id (:invoice_id args)))))
(def objects
{:invoice
{:fields {:id {:type :id}
:original_id {:type 'Int}
:client_identifier {:type 'String}
:total {:type 'String}
:source_url {:type 'String}
:outstanding_balance {:type 'String}
:invoice_number {:type 'String}
:status {:type 'String}
:expense_accounts {:type '(list :invoices_expense_accounts)}
:similarity {:type 'Float}
:date {:type :iso_date}
:due {:type :iso_date}
:client_id {:type 'Int}
:payments {:type '(list :invoice_payment)}
:vendor {:type :vendor}
:client {:type :client}
:scheduled_payment {:type :iso_date}}}
:invoices_expense_accounts
{:fields {:id {:type :id}
:invoice_id {:type 'String}
:account {:type :account}
:location {:type 'String}
:amount {:type 'String}}}
:invoice_page {:fields {:invoices {:type '(list :invoice)}
:outstanding {:type :money}
:count {:type 'Int}
:total {:type 'Int}
:start {:type 'Int}
:end {:type 'Int}}}})
(def queries
{:invoice_page {:type '(list :invoice_page)
:args {:import_status {:type 'String}
:exact_match_id {:type :id}
:date_range {:type :date_range}
:due_range {:type :date_range}
:status {:type :invoice_status}
:unresolved {:type 'Boolean}
:scheduled_payments {:type 'Boolean}
:client_id {:type :id}
:vendor_id {:type :id}
:amount_lte {:type :money}
:amount_gte {:type :money}
:invoice_number_like {:type 'String}
:location {:type 'String}
:start {:type 'Int}
:per_page {:type 'Int}
:sort {:type '(list :sort_item)}}
:resolve :get-invoice-page}
:all_invoices {:type '(list :invoice)
:args {:client_id {:type :id}
:client_code {:type 'String}
:original_id {:type 'Int}
:statuses {:type '(list String)}}
:resolve :get-all-invoices}})
(def mutations
{:edit_invoice {:type :invoice
:args {:invoice {:type :edit_invoice}}
:resolve :mutation/edit-invoice}
:add_invoice {:type :invoice
:args {:invoice {:type :add_invoice}}
:resolve :mutation/add-invoice}
:void_invoice {:type :invoice
:args {:invoice_id {:type :id}}
:resolve :mutation/void-invoice}
:unvoid_invoice {:type :invoice
:args {:invoice_id {:type :id}}
:resolve :mutation/unvoid-invoice}
:unautopay_invoice {:type :invoice
:args {:invoice_id {:type :id}}
:resolve :mutation/unautopay-invoice}
:reject_invoices {:type '(list :id)
:args {:invoices {:type '(list :id)}}
:resolve :mutation/reject-invoices}
:approve_invoices {:type '(list :id)
:args {:invoices {:type '(list :id)}}
:resolve :mutation/approve-invoices}
:add_and_print_invoice {:type :check_result
:args {:invoice {:type :add_invoice}
:bank_account_id {:type :id}
:type {:type :payment_type}}
:resolve :mutation/add-and-print-invoice}
:edit_expense_accounts {:type :invoice
:args {:invoice_id {:type :id}
:expense_accounts {:type '(list :edit_expense_account)}}
:resolve :mutation/edit-expense-accounts}})
(def input-objects
{:add_invoice
{:fields {:id {:type :id}
:invoice_number {:type 'String}
:expense_accounts {:type '(list :edit_expense_account)}
:location {:type 'String}
:scheduled_payment {:type :iso_date}
:date {:type :iso_date}
:due {:type :iso_date}
:client_id {:type :id}
:vendor_id {:type :id}
:vendor_name {:type 'String}
:total {:type :money}}}
:edit_invoice
{:fields {:id {:type :id}
:invoice_number {:type 'String}
:expense_accounts {:type '(list :edit_expense_account)}
:date {:type :iso_date}
:scheduled_payment {:type :iso_date}
:due {:type :iso_date}
:total {:type :money}}}
:edit_expense_account
{:fields {:id {:type :id}
:account_id {:type :id}
:location {:type 'String}
:amount {:type :money}}}})
(def enums
{:invoice_status {:values [{:enum-value :paid}
{:enum-value :unpaid}
{:enum-value :voided}]}})
(def resolvers
{:get-invoice-page get-invoice-page
:get-all-invoices get-all-invoices
:mutation/reject-invoices reject-invoices
:mutation/approve-invoices approve-invoices
:mutation/add-invoice add-invoice
:mutation/add-and-print-invoice add-and-print-invoice
:mutation/edit-invoice edit-invoice
:mutation/void-invoice void-invoice
:mutation/unvoid-invoice unvoid-invoice
:mutation/unautopay-invoice unautopay-invoice
:mutation/edit-expense-accounts edit-expense-accounts})
(defn attach [schema]
(->
(merge-with merge schema
{:objects objects
:queries queries
:mutations mutations
:input-objects input-objects
:enums enums})
(attach-resolvers resolvers)))

View File

@@ -91,7 +91,7 @@
(conj matches name))))
(filter #(<= (second %) threshold))
(sort-by second)
ffirst)
first)
word-set (set (filter (complement str/blank?) (str/split (.toLowerCase invoice-client-name) #"[\s:\-]" )))
client-word-match (->> clients
@@ -108,7 +108,7 @@
(filter (fn [[_ c]] (> c 0)))
(sort-by (fn [[_ c]] c))
reverse
ffirst)]
first)]
(or fuzzy-match client-word-match))))
(defn best-location-match [client text full-text]

View File

@@ -126,20 +126,20 @@
(defn import->invoice [{:keys [invoice-number source-url customer-identifier account-number total date vendor-code text full-text client-override vendor-override location-override]} clients]
(let [matching-client (cond
account-number (parse/best-match clients account-number 0.0)
customer-identifier (parse/best-match clients customer-identifier)
client-override (first (filter (fn [c]
(= (:db/id c) (Long/parseLong client-override)))
clients)))
matching-vendor (match-vendor vendor-code vendor-override)
matching-location (or (when-not (str/blank? location-override)
location-override)
(parse/best-location-match matching-client text full-text))]
(let [[matching-client similarity] (cond
account-number (parse/best-match clients account-number 0.0)
customer-identifier (parse/best-match clients customer-identifier)
client-override (first (filter (fn [c]
(= (:db/id c) (Long/parseLong client-override)))
clients)))
matching-vendor (match-vendor vendor-code vendor-override)
matching-location (or (when-not (str/blank? location-override)
location-override)
(parse/best-location-match matching-client text full-text))]
(remove-nils #:invoice {:invoice/client (:db/id matching-client)
:invoice/client-identifier (or account-number customer-identifier)
:invoice/vendor (:db/id matching-vendor)
:invoice/similarity (some-> similarity double (#(- 1.0 %)))
:invoice/source-url source-url
:invoice/invoice-number invoice-number
:invoice/total (Double/parseDouble total)

View File

@@ -45,7 +45,7 @@
:unpaid-invoices :unpaid
:paid-invoices :paid
:voided-invoices :voided)}
[[:invoices [:id :total :outstanding-balance :invoice-number :date :due :status :client-identifier :scheduled-payment :source-url
[[:invoices [:id :total :outstanding-balance :invoice-number :date :due :status :client-identifier :scheduled-payment :source-url :similarity
[:vendor [:name :id]]
[:expense_accounts [:amount :id :location
[:account [:id ]]]]
@@ -202,6 +202,7 @@
"File"]
[:td {:colspan 4}
[buttons/fa-icon {:icon "fa-external-link"
:target "_new"
:href source-url}]]])]]]]]
[:span {:style {:margin-right "1em"}}]])

View File

@@ -5,7 +5,7 @@
[auto-ap.subs :as subs]
[auto-ap.views.components.layouts :refer [side-bar-layout]]
[auto-ap.views.components.invoices.side-bar :refer [invoices-side-bar]]
[auto-ap.views.utils :refer [dispatch-event with-user]]
[auto-ap.views.utils :refer [dispatch-event with-user ->%]]
[auto-ap.utils :refer [by]]
[auto-ap.views.components.typeahead :refer [typeahead-v3]]
[auto-ap.views.components.invoice-table :refer [invoice-table] :as invoice-table]
@@ -16,7 +16,9 @@
[dropzone :as dz]
[auto-ap.views.pages.data-page :as data-page]
[clojure.set :as set]
[auto-ap.effects.forward :as forward]))
[auto-ap.effects.forward :as forward]
[goog.string :as gstring]
))
(defn dropzone []
@@ -69,9 +71,7 @@
[:span
[:span {:class "icon"}
[:i {:class "fa fa-cloud-download"}]]
"Drop any invoices you want to process here"]]]])})
))
"Drop any invoices you want to process here"]]]])})))
@@ -207,7 +207,13 @@
:data-page :import-invoices
:overrides {:client (fn [row]
[:p (:name (:client row))
[:p [:i.is-size-7 (:client-identifier row)]]])}
[:p [:i.is-size-7 (:client-identifier row)]
" "
[:span
{:style {:background-color (gstring/format "rgba(255, 0,0,%.2f)" (- 1.0 (:similarity row)))
}}
(->% (:similarity row))]]])}
:check-boxes true}]
[:span "No pending invoices"])]]]))
{:component-did-mount (fn []