Compare commits
7 Commits
test-plan-
...
47720692b9
| Author | SHA1 | Date | |
|---|---|---|---|
| 47720692b9 | |||
| 6b5d33a32f | |||
| d9d9263824 | |||
| ddbb6abc3a | |||
| 1a48abdd7c | |||
| ececdc8f5f | |||
| da7897c0d6 |
@@ -1,219 +0,0 @@
|
|||||||
---
|
|
||||||
title: Complete Automatic Sales Summary Calculations and Ledger Posting
|
|
||||||
type: feat
|
|
||||||
status: completed
|
|
||||||
date: 2026-04-24
|
|
||||||
---
|
|
||||||
|
|
||||||
# Complete Automatic Sales Summary Calculations and Ledger Posting
|
|
||||||
|
|
||||||
## What's Incomplete
|
|
||||||
- **Automatic Totals**: Aggregate attributes (e.g., `:sales-summary/total-card-payments`) are not calculated/stored by the job.
|
|
||||||
- **Data Persistence**: Automatic recalculations risk overwriting manual user adjustments.
|
|
||||||
- **Automation Gap**: Ledger entries are currently imported from external Excel files rather than generated automatically from the summaries.
|
|
||||||
- **UI Polish**: "Clientization" and HTMX context (`client-id`) TODOs remain in the admin interface.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
This plan completes the implementation of automatic sales summary calculations and ensures they are correctly posted to the ledger. Currently, the `sales-summaries-v2` job calculates detailed daily summaries, but it doesn't store aggregate totals, preserve manual adjustments, or trigger the creation of actual ledger entries. Additionally, the admin UI has several unresolved TODOs.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Problem Frame
|
|
||||||
|
|
||||||
The system currently aggregates raw sales data into a `sales-summary` entity, but the final step—creating balanced journal entries for the general ledger—is a manual process involving external Excel calculations and subsequent imports. This creates a dependency on external tools and increases the risk of data entry errors. The goal is to automate this pipeline entirely within the product.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Requirements Trace
|
|
||||||
|
|
||||||
- R1. Calculate and store aggregate totals (e.g., `:sales-summary/total-card-payments`) on the `sales-summary` entity.
|
|
||||||
- R2. Preserve user-made manual adjustments (`:sales-summary-item/manual? true`) during automatic recalculations.
|
|
||||||
- R3. Aggregate detailed `sales-summary-item`s into balanced `journal-entry` lines by account and location.
|
|
||||||
- R4. Automate the posting of these aggregated totals to the ledger.
|
|
||||||
- R5. Resolve UI TODOs in the Sales Summaries admin page, specifically regarding client-scoping ("clientize") and HTMX context (`client-id`).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Scope Boundaries
|
|
||||||
|
|
||||||
- **In-Scope**:
|
|
||||||
- Enhancements to the `sales-summaries-v2` job.
|
|
||||||
- Implementation of the summary-to-ledger aggregation and posting logic.
|
|
||||||
- Cleanup of the Sales Summaries admin UI.
|
|
||||||
- **Out-of-Scope**:
|
|
||||||
- Changing the fundamental calculation logic for sales orders/refunds.
|
|
||||||
- Creating new ledger accounts (assume existing account mapping is sufficient).
|
|
||||||
- Changing the naming of refunds/returns (user requested to keep as is).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Context & Research
|
|
||||||
|
|
||||||
### Relevant Code and Patterns
|
|
||||||
|
|
||||||
- **Jobs**: `src/clj/auto_ap/jobs/sales_summaries.clj` contains the main calculation logic.
|
|
||||||
- **UI**: `src/clj/auto_ap/ssr/admin/sales_summaries.clj` implements the admin interface.
|
|
||||||
- **Ledger Posting**: `src/clj/auto_ap/ledger.clj` and `iol_ion/src/iol_ion/tx/upsert_ledger.clj` handle journal entry creation.
|
|
||||||
- **Reconciliation Pattern**: `reconcile-ledger` in `src/clj/auto_ap/ledger.clj` shows how to find missing ledger entries and trigger their creation.
|
|
||||||
|
|
||||||
### Institutional Learnings
|
|
||||||
|
|
||||||
- No existing documented patterns for sales summary posting were found in `docs/solutions/`. This implementation will establish the pattern.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Key Technical Decisions
|
|
||||||
|
|
||||||
- **Detailed Summary $\to$ Aggregated Ledger**: The `sales-summary` will maintain granular detail (line items, specific fee types), but the ledger posting will aggregate these items by account and location to create balanced `journal-entry` lines.
|
|
||||||
- **Automatic Posting**: Posting to the ledger will be integrated into the reconciliation process, similar to how invoices and transactions are handled in `reconcile-ledger`.
|
|
||||||
- **Location Handling**: Since `sales-summary-item`s don't have a location, a default location for the client will be used for ledger posting.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Open Questions
|
|
||||||
|
|
||||||
### Resolved During Planning
|
|
||||||
|
|
||||||
- **Architectural Decision**: Use a detailed summary that aggregates into the ledger.
|
|
||||||
- **Renaming**: Keep "Refunds/Returns" as is.
|
|
||||||
|
|
||||||
### Deferred to Implementation
|
|
||||||
|
|
||||||
- **Default Location Logic**: Exactly how the "default location" for a client is retrieved or defined.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Implementation Units
|
|
||||||
|
|
||||||
- U1. **Enhance `sales-summaries-v2` Job**
|
|
||||||
|
|
||||||
**Goal:** Ensure the job stores aggregate totals and preserves manual adjustments.
|
|
||||||
|
|
||||||
**Requirements:** R1, R2
|
|
||||||
|
|
||||||
**Dependencies:** None
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `src/clj/auto_ap/jobs/sales_summaries.clj`
|
|
||||||
|
|
||||||
**Approach:**
|
|
||||||
- Update `sales-summaries-v2` to calculate totals for attributes like `:sales-summary/total-card-payments`, `:sales-summary/total-cash-payments`, etc., based on the generated items.
|
|
||||||
- Implement a merge strategy: when updating a summary, keep any items where `:sales-summary-item/manual?` is true, and only replace the automatically calculated items.
|
|
||||||
|
|
||||||
**Test scenarios:**
|
|
||||||
- Happy path: Running the job for a client with sales and refunds results in a `sales-summary` with correct `:sales-summary/total-*` attributes.
|
|
||||||
- Edge case: Running the job on a summary that already has a manual item ensures the manual item is not overwritten.
|
|
||||||
|
|
||||||
**Verification:**
|
|
||||||
- Datomic query shows `sales-summary` entities have populated total attributes and preserved manual items.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
- U2. **Implement Summary-to-Ledger Aggregation**
|
|
||||||
|
|
||||||
**Goal:** Create a function to transform detailed summary items into balanced ledger lines.
|
|
||||||
|
|
||||||
**Requirements:** R3
|
|
||||||
|
|
||||||
**Dependencies:** U1
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `src/clj/auto_ap/ledger/sales_summaries.clj` (or add to `src/clj/auto_ap/ledger.clj`)
|
|
||||||
- Test: `test/clj/auto_ap/ledger_test.clj`
|
|
||||||
|
|
||||||
**Approach:**
|
|
||||||
- Create a function `aggregate-summary-items` that:
|
|
||||||
1. Groups `sales-summary-item`s by `:ledger-mapped/account`.
|
|
||||||
2. Sums the `:ledger-mapped/amount` based on `:ledger-mapped/ledger-side` (debit vs credit).
|
|
||||||
3. Assigns a location (default client location).
|
|
||||||
4. Returns a list of `journal-entry-line` maps.
|
|
||||||
|
|
||||||
**Test scenarios:**
|
|
||||||
- Happy path: A set of items with mixed accounts and sides aggregates into the correct number of ledger lines with summed amounts.
|
|
||||||
- Edge case: Items with `nil` accounts are handled gracefully (e.g., mapped to an "Unknown" account or logged as error).
|
|
||||||
|
|
||||||
**Verification:**
|
|
||||||
- Unit tests verify that a list of `sales-summary-item`s is correctly transformed into `journal-entry-line`s.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
- U3. **Implement Automatic Ledger Posting for Summaries**
|
|
||||||
|
|
||||||
**Goal:** Ensure sales summaries trigger the creation of ledger entries.
|
|
||||||
|
|
||||||
**Requirements:** R4
|
|
||||||
|
|
||||||
**Dependencies:** U2
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `src/clj/auto_ap/ledger.clj`
|
|
||||||
- Modify: `src/clj/auto_ap/jobs/sales_summaries.clj`
|
|
||||||
|
|
||||||
**Approach:**
|
|
||||||
- Implement a `:upsert-sales-summary-ledger` transaction or function that takes a `sales-summary` and uses the aggregation logic from U2 to post to the ledger.
|
|
||||||
- Integrate this into the `reconcile-ledger` function in `src/clj/auto_ap/ledger.clj` to find summaries missing ledger entries and post them.
|
|
||||||
|
|
||||||
**Test scenarios:**
|
|
||||||
- Integration: Running `reconcile-ledger` identifies a `sales-summary` missing a `journal-entry` and creates a balanced `journal-entry` for it.
|
|
||||||
- Happy path: The created `journal-entry` has the correct total amount and matches the summary totals.
|
|
||||||
|
|
||||||
**Verification:**
|
|
||||||
- A `sales-summary` entity is linked to a `journal-entry` via `:journal-entry/original-entity`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
- U4. **Resolve UI TODOs in Sales Summaries Admin**
|
|
||||||
|
|
||||||
**Goal:** Fix client-scoping and HTMX context in the admin UI.
|
|
||||||
|
|
||||||
**Requirements:** R5
|
|
||||||
|
|
||||||
**Dependencies:** None
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `src/clj/auto_ap/ssr/admin/sales_summaries.clj`
|
|
||||||
|
|
||||||
**Approach:**
|
|
||||||
- Resolve "clientize" TODOs: Ensure the data pulled for the table and edit wizard is correctly scoped and transformed using client-specific context.
|
|
||||||
- Fix HTMX `client-id` passing: Update the `new-summary-item` trigger to correctly pass the `client-id` via `hx-vals` from the form state.
|
|
||||||
- Clean up any remaining schema TODOs in the SSR file.
|
|
||||||
|
|
||||||
**Test scenarios:**
|
|
||||||
- Integration: Adding a new summary item in the UI correctly sends the `client-id` and the item is created for the correct client.
|
|
||||||
- Happy path: The summary table displays correctly and "missing account" warnings appear only for items without a mapped account.
|
|
||||||
|
|
||||||
**Verification:**
|
|
||||||
- Manual verification in the browser: New items are added correctly, and the UI is free of "missing account" red pills for mapped items.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## System-Wide Impact
|
|
||||||
|
|
||||||
- **Interaction graph**: The `sales-summaries-v2` job now feeds into the ledger system via `reconcile-ledger`.
|
|
||||||
- **Error propagation**: Failures in the aggregation logic will prevent the `journal-entry` from being created, which will be surfaced by `reconcile-ledger` as a missing entry.
|
|
||||||
- **State lifecycle risks**: Ensuring that `manual?` items are not overwritten during automatic recalculation is critical to avoid losing user adjustments.
|
|
||||||
- **Integration coverage**: Integration tests must cover the full flow: `sales-orders` $\to$ `sales-summary` $\to$ `journal-entry`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Risks & Dependencies
|
|
||||||
|
|
||||||
| Risk | Mitigation |
|
|
||||||
|------|------------|
|
|
||||||
| Overwriting manual adjustments | Implement explicit merge logic based on the `:sales-summary-item/manual?` flag. |
|
|
||||||
| Unbalanced ledger entries | Use a strict aggregation function that ensures debits = credits for every posted summary. |
|
|
||||||
| Missing location data | Implement a robust fallback to a default client location. |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Sources & References
|
|
||||||
|
|
||||||
- Related code: `src/clj/auto_ap/jobs/sales_summaries.clj`
|
|
||||||
- Related code: `src/clj/auto_ap/ssr/admin/sales_summaries.clj`
|
|
||||||
- Related code: `src/clj/auto_ap/ledger.clj`
|
|
||||||
- Related code: `iol_ion/src/iol_ion/tx/upsert_ledger.clj`
|
|
||||||
@@ -49,9 +49,9 @@
|
|||||||
(datomic-fn :upsert-entity #'iol-ion.tx.upsert-entity/upsert-entity)
|
(datomic-fn :upsert-entity #'iol-ion.tx.upsert-entity/upsert-entity)
|
||||||
(datomic-fn :upsert-invoice #'iol-ion.tx.upsert-invoice/upsert-invoice)
|
(datomic-fn :upsert-invoice #'iol-ion.tx.upsert-invoice/upsert-invoice)
|
||||||
(datomic-fn :upsert-ledger #'iol-ion.tx.upsert-ledger/upsert-ledger)
|
(datomic-fn :upsert-ledger #'iol-ion.tx.upsert-ledger/upsert-ledger)
|
||||||
(datomic-fn :upsert-transaction #'iol-ion.tx.upsert-transaction/upsert-transaction)
|
(datomic-fn :upsert-transaction #'iol-ion.tx.upsert-transaction/upsert-transaction)])))
|
||||||
(datomic-fn :upsert-sales-summary #'iol-ion.tx.upsert-sales-summary-ledger/upsert-sales-summary)])))
|
|
||||||
|
|
||||||
(comment
|
(comment
|
||||||
(regenerate-literals)
|
(regenerate-literals)
|
||||||
(auto-ap.datomic/install-functions))
|
|
||||||
|
(auto-ap.datomic/install-functions))
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
(ns iol-ion.tx.upsert-sales-summary-ledger
|
|
||||||
(:require [datomic.api :as dc]))
|
|
||||||
|
|
||||||
(defn summary->journal-entry [db summary-id]
|
|
||||||
(let [summary (dc/pull db '[:sales-summary/client
|
|
||||||
:sales-summary/date
|
|
||||||
{:sales-summary/items [:sales-summary-item/category
|
|
||||||
:ledger-mapped/account
|
|
||||||
:ledger-mapped/amount
|
|
||||||
{:ledger-mapped/ledger-side [:db/ident]}]}]
|
|
||||||
summary-id)
|
|
||||||
items (:sales-summary/items summary)
|
|
||||||
aggregated (->> items
|
|
||||||
(filter :ledger-mapped/account)
|
|
||||||
(group-by :ledger-mapped/account)
|
|
||||||
(map (fn [[account acc-items]]
|
|
||||||
(reduce
|
|
||||||
(fn [m item]
|
|
||||||
(update m (:db/ident (:ledger-mapped/ledger-side item)) (fnil + 0.0) (:ledger-mapped/amount item 0.0)))
|
|
||||||
{:account account}
|
|
||||||
acc-items))))
|
|
||||||
_ (clojure.pprint/pprint aggregated)
|
|
||||||
line-items (mapv (fn [{:keys [account] :as m}]
|
|
||||||
(cond-> {:db/id (str (java.util.UUID/randomUUID))
|
|
||||||
:journal-entry-line/account account
|
|
||||||
:journal-entry-line/location "A"}
|
|
||||||
(get m :ledger-side/debit) (assoc :journal-entry-line/debit (get m :ledger-side/debit))
|
|
||||||
(get m :ledger-side/credit) (assoc :journal-entry-line/credit (get m :ledger-side/credit))))
|
|
||||||
aggregated)
|
|
||||||
|
|
||||||
total-debits (reduce + 0.0 (map #(get % :ledger-side/debit 0.0) aggregated))
|
|
||||||
total-credits (reduce + 0.0 (map #(get % :ledger-side/credit 0.0) aggregated))
|
|
||||||
_ (clojure.pprint/pprint [total-debits total-credits])
|
|
||||||
]
|
|
||||||
(when (and (seq line-items)
|
|
||||||
(= (Math/round (* 1000 total-debits))
|
|
||||||
(Math/round (* 1000 total-credits))))
|
|
||||||
{:journal-entry/source "sales-summary"
|
|
||||||
:journal-entry/client (:db/id (:sales-summary/client summary))
|
|
||||||
:journal-entry/date (:sales-summary/date summary)
|
|
||||||
:journal-entry/original-entity summary-id
|
|
||||||
:journal-entry/amount total-debits
|
|
||||||
:journal-entry/line-items line-items})))
|
|
||||||
|
|
||||||
(defn current-date [db]
|
|
||||||
(let [last-tx (dc/t->tx (dc/basis-t db))
|
|
||||||
[[date]] (seq (dc/q '[:find ?ti :in $ ?tx
|
|
||||||
:where [?tx :db/txInstant ?ti]]
|
|
||||||
db
|
|
||||||
last-tx))]
|
|
||||||
date))
|
|
||||||
|
|
||||||
(defn upsert-sales-summary [db summary]
|
|
||||||
(let [upserted-summary [[:upsert-entity summary]]
|
|
||||||
db-after (-> (dc/with db upserted-summary) :db-after)
|
|
||||||
summary-id (:db/id summary)
|
|
||||||
client-id (-> (dc/pull db-after [{:sales-summary/client [:db/id]}] summary-id)
|
|
||||||
:sales-summary/client
|
|
||||||
:db/id)
|
|
||||||
journal-entry (summary->journal-entry db-after summary-id)]
|
|
||||||
upserted-summary
|
|
||||||
#_(into upserted-summary
|
|
||||||
(if journal-entry
|
|
||||||
[[:upsert-ledger journal-entry]]
|
|
||||||
(concat
|
|
||||||
[[:db/retractEntity [:journal-entry/original-entity (:db/id summary)]]]
|
|
||||||
|
|
||||||
|
|
||||||
(when client-id [{:db/id client-id
|
|
||||||
:client/ledger-last-change (current-date db)}]))))))
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1954,12 +1954,12 @@
|
|||||||
:db/unique :db.unique/identity
|
:db/unique :db.unique/identity
|
||||||
:db/index true}
|
:db/index true}
|
||||||
{:db/ident :sales-summary/client+dirty
|
{:db/ident :sales-summary/client+dirty
|
||||||
:db/valueType :db.type/tuple
|
:db/valueType :db.type/tuple
|
||||||
:db/tupleAttrs [:sales-summary/client :sales-summary/dirty]
|
:db/tupleAttrs [:sales-summary/client :sales-summary/dirty]
|
||||||
:db/cardinality :db.cardinality/one
|
:db/cardinality :db.cardinality/one
|
||||||
:db/index true}
|
:db/index true}
|
||||||
|
|
||||||
{:db/ident :sales-summary-item/category
|
{:db/ident :sales-summary-item/category
|
||||||
:db/valueType :db.type/string
|
:db/valueType :db.type/string
|
||||||
:db/cardinality :db.cardinality/one}
|
:db/cardinality :db.cardinality/one}
|
||||||
{:db/ident :sales-summary-item/sort-order
|
{:db/ident :sales-summary-item/sort-order
|
||||||
|
|||||||
@@ -5,11 +5,10 @@
|
|||||||
[iol-ion.tx.propose-invoice]
|
[iol-ion.tx.propose-invoice]
|
||||||
[iol-ion.tx.reset-rels]
|
[iol-ion.tx.reset-rels]
|
||||||
[iol-ion.tx.reset-scalars]
|
[iol-ion.tx.reset-scalars]
|
||||||
[iol-ion.tx.upsert-entity]
|
[iol-ion.tx.upsert-entity]
|
||||||
[iol-ion.tx.upsert-invoice]
|
[iol-ion.tx.upsert-invoice]
|
||||||
[iol-ion.tx.upsert-ledger]
|
[iol-ion.tx.upsert-ledger]
|
||||||
[iol-ion.tx.upsert-transaction]
|
[iol-ion.tx.upsert-transaction]
|
||||||
[iol-ion.tx.upsert-sales-summary-ledger]
|
|
||||||
[com.github.ivarref.gen-fn :refer [gen-fn! datomic-fn]]
|
[com.github.ivarref.gen-fn :refer [gen-fn! datomic-fn]]
|
||||||
[auto-ap.utils :refer [default-pagination-size by]]
|
[auto-ap.utils :refer [default-pagination-size by]]
|
||||||
[clojure.edn :as edn]
|
[clojure.edn :as edn]
|
||||||
|
|||||||
@@ -278,42 +278,46 @@
|
|||||||
|
|
||||||
(defn sales-summaries-v2 []
|
(defn sales-summaries-v2 []
|
||||||
(doseq [[c client-code] (dc/q '[:find ?c ?client-code
|
(doseq [[c client-code] (dc/q '[:find ?c ?client-code
|
||||||
:in $
|
:in $
|
||||||
:where [?c :client/code ?client-code]]
|
:where [?c :client/code ?client-code]]
|
||||||
(dc/db conn))
|
(dc/db conn))
|
||||||
{:sales-summary/keys [date] :db/keys [id] :as existing-summary} (dirty-sales-summaries c)]
|
{:sales-summary/keys [date] :db/keys [id]} (dirty-sales-summaries c)]
|
||||||
(mu/with-context {:client-code client-code
|
(mu/with-context {:client-code client-code
|
||||||
:date date}
|
:date date}
|
||||||
(alog/info ::updating)
|
(alog/info ::updating)
|
||||||
(let [manual-items (->> existing-summary
|
(let [result {:db/id id
|
||||||
:sales-summary/items
|
:sales-summary/client c
|
||||||
(filter :sales-summary-item/manual?))
|
:sales-summary/date date
|
||||||
calculated-items (->>
|
:sales-summary/dirty false
|
||||||
(get-sales c date)
|
:sales-summary/client+date [c date]
|
||||||
(concat (get-payment-items c date))
|
|
||||||
(concat (get-refund-items c date))
|
:sales-summary/items
|
||||||
(cons (get-discounts c date))
|
(->>
|
||||||
(cons (get-fees c date))
|
(get-sales c date)
|
||||||
(cons (get-tax c date))
|
(concat (get-payment-items c date))
|
||||||
(cons (get-tip c date))
|
(concat (get-refund-items c date))
|
||||||
(cons (get-returns c date))
|
(cons (get-discounts c date))
|
||||||
(filter identity)
|
(cons (get-fees c date))
|
||||||
(map (fn [z]
|
(cons (get-tax c date))
|
||||||
(assoc z :ledger-mapped/account (some-> z :sales-summary-item/category str/lower-case name->number lookup-account)
|
(cons (get-tip c date))
|
||||||
:sales-summary-item/manual? false))))
|
(cons (get-returns c date))
|
||||||
all-items (concat calculated-items manual-items)
|
(filter identity)
|
||||||
result {:db/id id
|
(map (fn [z]
|
||||||
:sales-summary/client c
|
(assoc z :ledger-mapped/account (some-> z :sales-summary-item/category str/lower-case name->number lookup-account)
|
||||||
:sales-summary/date date
|
:sales-summary-item/manual? false))
|
||||||
:sales-summary/dirty false
|
)) }]
|
||||||
:sales-summary/client+date [c date]
|
(if (seq (:sales-summary/items result))
|
||||||
:sales-summary/items all-items}]
|
(do
|
||||||
(if (seq (:sales-summary/items result))
|
(alog/info ::upserting-summaries
|
||||||
(do
|
:category-count (count (:sales-summary/items result)))
|
||||||
(alog/info ::upserting-summaries
|
@(dc/transact conn [[:upsert-entity result]]))
|
||||||
:category-count (count (:sales-summary/items result)))
|
@(dc/transact conn [{:db/id id :sales-summary/dirty false}]))))))
|
||||||
@(dc/transact conn [[:upsert-sales-summary result]]))
|
|
||||||
@(dc/transact conn [{:db/id id :sales-summary/dirty false}]))))))
|
(let [c (auto-ap.datomic/pull-attr (dc/db conn) :db/id [:client/code "NGCL" ])
|
||||||
|
date #inst "2024-04-14T00:00:00-07:00"]
|
||||||
|
(get-payment-items c date)
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
(defn reset-summaries []
|
(defn reset-summaries []
|
||||||
@@ -330,39 +334,29 @@
|
|||||||
(comment
|
(comment
|
||||||
(auto-ap.datomic/transact-schema conn)
|
(auto-ap.datomic/transact-schema conn)
|
||||||
|
|
||||||
|
@(dc/transact conn [{:db/ident :sales-summary/total-unknown-processor-payments
|
||||||
|
:db/noHistory true,
|
||||||
|
:db/valueType :db.type/double
|
||||||
|
:db/cardinality :db.cardinality/one}])
|
||||||
|
|
||||||
(apply mark-dirty [:client/code "NGCL"] (last-n-days 30))
|
(apply mark-dirty [:client/code "NGCL"] (last-n-days 30))
|
||||||
|
|
||||||
(apply mark-dirty [:client/code "NGDG"] (last-n-days 30))
|
(apply mark-dirty [:client/code "NGDG"] (last-n-days 30))
|
||||||
|
|
||||||
(dirty-sales-summaries [:client/code "NGWH"])
|
(apply mark-dirty [:client/code "NGPG"] (last-n-days 30))
|
||||||
|
|
||||||
|
(mark-all-dirty 50)
|
||||||
(apply mark-dirty [:client/code "NGWH"] (last-n-days 5))
|
|
||||||
|
|
||||||
(iol-ion.tx.upsert-sales-summary-ledger/summary->journal-entry (dc/db conn) 17592314245819)
|
|
||||||
|
|
||||||
|
|
||||||
(iol-ion.tx.upsert-sales-summary-ledger/upsert-sales-summary (dc/db conn) {:db/id 17592314241429})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(mark-all-dirty 5)
|
|
||||||
(delete-all)
|
(delete-all)
|
||||||
|
|
||||||
|
|
||||||
(sales-summaries-v2)
|
(sales-summaries-v2)
|
||||||
|
|
||||||
|
|
||||||
1
|
|
||||||
|
|
||||||
|
|
||||||
(dc/q '[:find (pull ?sos [* {:sales-summary/sales-items [*]}])
|
(dc/q '[:find (pull ?sos [* {:sales-summary/sales-items [*]}])
|
||||||
:in $
|
:in $
|
||||||
:where [?sos :sales-summary/client [:client/code "NGHW"]]
|
:where [?sos :sales-summary/client [:client/code "NGHW"]]
|
||||||
[?sos :sales-summary/date ?d]
|
[?sos :sales-summary/date ?d]
|
||||||
[(= ?d #inst "2024-04-10T00:00:00-07:00")]]
|
[(= ?d #inst "2024-04-10T00:00:00-07:00")]]
|
||||||
(dc/db conn))
|
(dc/db conn))
|
||||||
|
|
||||||
(dc/q '[:find ?n ?p2 (sum ?total)
|
(dc/q '[:find ?n ?p2 (sum ?total)
|
||||||
:with ?c
|
:with ?c
|
||||||
:in $ [?clients ?start-date ?end-date]
|
:in $ [?clients ?start-date ?end-date]
|
||||||
@@ -375,21 +369,18 @@
|
|||||||
(dc/db conn)
|
(dc/db conn)
|
||||||
[[(auto-ap.datomic/pull-attr (dc/db conn) :db/id [:client/code "NGHW"])] #inst "2024-04-11T00:00:00-07:00" #inst "2024-04-11T00:00:00-07:00"])
|
[[(auto-ap.datomic/pull-attr (dc/db conn) :db/id [:client/code "NGHW"])] #inst "2024-04-11T00:00:00-07:00" #inst "2024-04-11T00:00:00-07:00"])
|
||||||
|
|
||||||
(dc/q '[:find ?n
|
(dc/q '[:find ?n
|
||||||
:in $ [?clients ?start-date ?end-date]
|
:in $ [?clients ?start-date ?end-date]
|
||||||
:where [(iol-ion.query/scan-sales-orders $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]
|
:where [(iol-ion.query/scan-sales-orders $ ?clients ?start-date ?end-date) [[?e _ ?sort-default] ...]]
|
||||||
[?e :sales-order/line-items ?li]
|
[?e :sales-order/line-items ?li]
|
||||||
[?li :order-line-item/item-name ?n]]
|
[?li :order-line-item/item-name ?n] ]
|
||||||
(dc/db conn)
|
(dc/db conn)
|
||||||
[[(auto-ap.datomic/pull-attr (dc/db conn) :db/id [:client/code "NGCL"])] #inst "2024-04-11T00:00:00-07:00" #inst "2024-04-24T00:00:00-07:00"])
|
[[(auto-ap.datomic/pull-attr (dc/db conn) :db/id [:client/code "NGCL"])] #inst "2024-04-11T00:00:00-07:00" #inst "2024-04-24T00:00:00-07:00"])
|
||||||
|
|
||||||
|
@(dc/transact conn [{:db/id :sales-summary/total-tax :db/ident :sales-summary/total-tax-legacy}
|
||||||
|
{:db/id :sales-summary/total-tip :db/ident :sales-summary/total-tip-legacy}])
|
||||||
|
|
||||||
@(dc/transact conn [{:db/id :sales-summary/total-tax :db/ident :sales-summary/total-tax-legacy}
|
(auto-ap.datomic/transact-schema conn)
|
||||||
{:db/id :sales-summary/total-tip :db/ident :sales-summary/total-tip-legacy}])
|
|
||||||
|
|
||||||
(auto-ap.datomic/transact-schema conn)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -35,42 +35,27 @@
|
|||||||
|
|
||||||
|
|
||||||
invoices-missing-ledger-entries (->> (dc/q {:find ['?t ]
|
invoices-missing-ledger-entries (->> (dc/q {:find ['?t ]
|
||||||
:in ['$ '?sd]
|
:in ['$ '?sd]
|
||||||
:where ['[?t :invoice/date ?d]
|
:where ['[?t :invoice/date ?d]
|
||||||
'[(>= ?d ?sd)]
|
'[(>= ?d ?sd)]
|
||||||
'(not [_ :journal-entry/original-entity ?t])
|
'(not [_ :journal-entry/original-entity ?t])
|
||||||
'[?t :invoice/total ?amt]
|
'[?t :invoice/total ?amt]
|
||||||
'[(not= 0.0 ?amt)]
|
'[(not= 0.0 ?amt)]
|
||||||
'(not [?t :invoice/status :invoice-status/voided])
|
'(not [?t :invoice/status :invoice-status/voided])
|
||||||
'(not [?t :invoice/import-status :import-status/pending])
|
'(not [?t :invoice/import-status :import-status/pending])
|
||||||
'(not [?t :invoice/exclude-from-ledger true])
|
'(not [?t :invoice/exclude-from-ledger true])
|
||||||
]}
|
]}
|
||||||
(dc/db conn) start-date)
|
(dc/db conn) start-date)
|
||||||
(map first)
|
(map first)
|
||||||
(mapv (fn [i]
|
(mapv (fn [i]
|
||||||
[:upsert-invoice {:db/id i}])))
|
[:upsert-invoice {:db/id i}])))
|
||||||
|
repairs (vec (concat txes-missing-ledger-entries invoices-missing-ledger-entries))]
|
||||||
sales-summaries-missing-ledger-entries (->> (dc/q {:find ['?ss ]
|
|
||||||
:in ['$ '?sd]
|
|
||||||
:where ['[?ss :sales-summary/date ?d]
|
|
||||||
'[(>= ?d ?sd)]
|
|
||||||
'(not [_ :journal-entry/original-entity ?ss])
|
|
||||||
'[?ss :sales-summary/items ?item]
|
|
||||||
'[?item :ledger-mapped/account]
|
|
||||||
]}
|
|
||||||
(dc/db conn) start-date)
|
|
||||||
(map first)
|
|
||||||
(mapv (fn [ss]
|
|
||||||
[:upsert-sales-summary {:db/id ss}])))
|
|
||||||
|
|
||||||
repairs (vec (concat txes-missing-ledger-entries invoices-missing-ledger-entries sales-summaries-missing-ledger-entries))]
|
|
||||||
(when (seq repairs)
|
(when (seq repairs)
|
||||||
(mu/log ::ledger-repairs-needed
|
(mu/log ::ledger-repairs-needed
|
||||||
:sample (take 3 repairs)
|
:sample (take 3 repairs)
|
||||||
:transaction-count (count txes-missing-ledger-entries)
|
:transaction-count (count txes-missing-ledger-entries)
|
||||||
:invoice-count (count invoices-missing-ledger-entries)
|
:invoice-count (count invoices-missing-ledger-entries))
|
||||||
:sales-summary-count (count sales-summaries-missing-ledger-entries))
|
@(dc/transact conn repairs)))))
|
||||||
@(dc/transact conn repairs)))))
|
|
||||||
|
|
||||||
|
|
||||||
(defn touch-transaction [e]
|
(defn touch-transaction [e]
|
||||||
|
|||||||
@@ -177,9 +177,8 @@
|
|||||||
(conj [:paragraph {:color [128 0 0] :size 9} (:warning report)])
|
(conj [:paragraph {:color [128 0 0] :size 9} (:warning report)])
|
||||||
(conj
|
(conj
|
||||||
(table->pdf report
|
(table->pdf report
|
||||||
(cond-> (into [30 ] (repeat client-count 13))
|
(cond-> (into [30 ] (repeat client-count 13))
|
||||||
(:include-comparison args) (into (repeat (* 2 client-count) 13))
|
(:include-comparison args) (into (repeat (* 2 client-count) 13))))))
|
||||||
(and (> client-count 1) (not (:include-comparison args))) (conj 13)))))
|
|
||||||
output-stream)
|
output-stream)
|
||||||
(.toByteArray output-stream)))
|
(.toByteArray output-stream)))
|
||||||
|
|
||||||
|
|||||||
@@ -1,70 +1,85 @@
|
|||||||
(ns auto-ap.ssr.pos.sales-summaries
|
(ns auto-ap.ssr.admin.sales-summaries
|
||||||
(:require
|
(:require
|
||||||
[auto-ap.datomic
|
[auto-ap.datomic
|
||||||
:refer [apply-pagination apply-sort-3 conn merge-query pull-many
|
:refer [apply-pagination apply-sort-3 conn merge-query pull-many
|
||||||
query2]]
|
query2]]
|
||||||
[auto-ap.datomic.accounts :as d-accounts]
|
[auto-ap.datomic.accounts :as d-accounts]
|
||||||
[auto-ap.graphql.utils :refer [extract-client-ids]]
|
[auto-ap.graphql.utils :refer [extract-client-ids]]
|
||||||
[auto-ap.query-params :refer [wrap-copy-qp-pqp]]
|
[auto-ap.query-params :refer [wrap-copy-qp-pqp]]
|
||||||
[auto-ap.client-routes :as client-routes]
|
[auto-ap.routes.admin.sales-summaries :as route]
|
||||||
[auto-ap.routes.pos.sales-summaries :as route]
|
[auto-ap.routes.utils
|
||||||
|
:refer [wrap-admin wrap-client-redirect-unauthenticated]]
|
||||||
[auto-ap.ssr-routes :as ssr-routes]
|
[auto-ap.ssr-routes :as ssr-routes]
|
||||||
[auto-ap.ssr.common-handlers :refer [add-new-entity-handler]]
|
[auto-ap.ssr.common-handlers :refer [add-new-entity-handler]]
|
||||||
[auto-ap.ssr.components :as com]
|
[auto-ap.ssr.components :as com]
|
||||||
[auto-ap.ssr.components.link-dropdown :refer [link-dropdown]]
|
|
||||||
[auto-ap.ssr.components.multi-modal :as mm]
|
[auto-ap.ssr.components.multi-modal :as mm]
|
||||||
[auto-ap.ssr.form-cursor :as fc]
|
[auto-ap.ssr.form-cursor :as fc]
|
||||||
[auto-ap.ssr.grid-page-helper :as helper :refer [wrap-apply-sort]]
|
[auto-ap.ssr.grid-page-helper :as helper :refer [wrap-apply-sort]]
|
||||||
[auto-ap.ssr.hx :as hx]
|
[auto-ap.ssr.hx :as hx]
|
||||||
[auto-ap.ssr.pos.common
|
|
||||||
:refer [date-range-field*]]
|
|
||||||
[auto-ap.ssr.svg :as svg]
|
[auto-ap.ssr.svg :as svg]
|
||||||
[auto-ap.ssr.utils
|
[auto-ap.ssr.utils
|
||||||
:refer [apply-middleware-to-all-handlers clj-date-schema
|
:refer [apply-middleware-to-all-handlers clj-date-schema
|
||||||
default-grid-fields-schema entity-id html-response money
|
default-grid-fields-schema entity-id html-response money
|
||||||
strip temp-id wrap-merge-prior-hx wrap-schema-enforce]]
|
strip temp-id wrap-merge-prior-hx wrap-schema-enforce]]
|
||||||
[auto-ap.time :as atime]
|
[auto-ap.time :as atime]
|
||||||
[bidi.bidi :as bidi]
|
[bidi.bidi :as bidi]
|
||||||
[clj-time.coerce :as c]
|
[clj-time.coerce :as c]
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[datomic.api :as dc]
|
[datomic.api :as dc]
|
||||||
[hiccup.util :as hu]
|
[hiccup.util :as hu]
|
||||||
[iol-ion.query :refer [dollars= dollars-0?]]
|
[iol-ion.query :refer [dollars=]]
|
||||||
[malli.core :as mc]
|
[malli.core :as mc]
|
||||||
[malli.util :as mut]))
|
[malli.util :as mut]))
|
||||||
|
|
||||||
(def query-schema (mc/schema
|
(def query-schema (mc/schema
|
||||||
[:maybe
|
[:maybe
|
||||||
(into [:map {:date-range [:date-range :start-date :end-date]}
|
(into [:map {:date-range [:date-range :start-date :end-date]}
|
||||||
|
|
||||||
[:start-date {:optional true}
|
[:start-date {:optional true}
|
||||||
[:maybe clj-date-schema]]
|
[:maybe clj-date-schema]]
|
||||||
[:end-date {:optional true}
|
[:end-date {:optional true}
|
||||||
[:maybe clj-date-schema]]]
|
[:maybe clj-date-schema]] ]
|
||||||
default-grid-fields-schema)]))
|
default-grid-fields-schema)]))
|
||||||
|
|
||||||
(defn filters [request]
|
(defn filters [request]
|
||||||
[:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms"
|
[:form {"hx-trigger" "change delay:500ms, keyup changed from:.hot-filter delay:1000ms"
|
||||||
"hx-get" (bidi/path-for ssr-routes/only-routes
|
"hx-get" (bidi/path-for ssr-routes/only-routes
|
||||||
::route/table)
|
::route/table)
|
||||||
"hx-target" "#entity-table"
|
"hx-target" "#entity-table"
|
||||||
"hx-indicator" "#entity-table"}
|
"hx-indicator" "#entity-table"}
|
||||||
|
|
||||||
[:fieldset.space-y-6
|
#_[:fieldset.space-y-6
|
||||||
(date-range-field* request)]])
|
(date-range-field {:value {:start (:start-date (:query-params request))
|
||||||
|
:end (:end-date (:query-params request))}
|
||||||
|
:id "date-range"})
|
||||||
|
(com/field {:label "Source"}
|
||||||
|
(com/select {:name "source"
|
||||||
|
:class "hot-filter w-full"
|
||||||
|
:value (:source (:query-params request))
|
||||||
|
:placeholder ""
|
||||||
|
:options (ref->select-options "import-source" :allow-nil? true)}))
|
||||||
|
|
||||||
|
#_(com/field {:label "Code"}
|
||||||
|
(com/text-input {:name "code"
|
||||||
|
:id "code"
|
||||||
|
:class "hot-filter"
|
||||||
|
:value (:code (:query-params request))
|
||||||
|
:placeholder "11101"
|
||||||
|
:size :small}))]])
|
||||||
|
|
||||||
(def default-read '[:db/id
|
(def default-read '[:db/id
|
||||||
*
|
*
|
||||||
[:sales-summary/date :xform clj-time.coerce/from-date]
|
[:sales-summary/date :xform clj-time.coerce/from-date]
|
||||||
{:sales-summary/client [:client/code :client/name :db/id]}
|
{:sales-summary/client [:client/code :client/name :db/id]}
|
||||||
{:sales-summary/items [{[:ledger-mapped/ledger-side :xform iol-ion.query/ident] [:db/ident]}
|
{:sales-summary/items [{[:ledger-mapped/ledger-side :xform iol-ion.query/ident] [:db/ident]
|
||||||
:ledger-mapped/account
|
} ;; TODO clientize
|
||||||
:ledger-mapped/amount
|
:ledger-mapped/account
|
||||||
:sales-summary-item/category
|
:ledger-mapped/amount
|
||||||
:sales-summary-item/sort-order
|
:sales-summary-item/category
|
||||||
|
:sales-summary-item/sort-order
|
||||||
:db/id
|
:db/id
|
||||||
:sales-summary-item/manual?]}
|
:sales-summary-item/manual?]
|
||||||
{:journal-entry/original-entity [:db/id]}])
|
} ]) ;; TODO
|
||||||
|
|
||||||
(defn fetch-ids [db request]
|
(defn fetch-ids [db request]
|
||||||
(let [query-params (:query-params request)
|
(let [query-params (:query-params request)
|
||||||
@@ -73,26 +88,27 @@
|
|||||||
(:client-id query-params)
|
(:client-id query-params)
|
||||||
(when (:client-code query-params)
|
(when (:client-code query-params)
|
||||||
[:client/code (:client-code query-params)]))
|
[:client/code (:client-code query-params)]))
|
||||||
query (cond-> {:query {:find []
|
query (cond-> {:query {:find []
|
||||||
:in '[$ [?client ...]]
|
:in '[$ [?client ...]]
|
||||||
:where '[[?e :sales-summary/client ?client]]}
|
:where '[[?e :sales-summary/client ?client]]}
|
||||||
:args [db valid-clients]}
|
:args [db valid-clients]}
|
||||||
(or (:start-date query-params)
|
(or (:start-date query-params)
|
||||||
(:end-date query-params))
|
(:end-date query-params))
|
||||||
(merge-query {:query '{:where [[?e :sales-summary/date ?d]]}})
|
(merge-query {:query '{:where [[?e :sales-summary/date ?d]]}})
|
||||||
|
|
||||||
(:start-date query-params)
|
(:start-date query-params)
|
||||||
(merge-query {:query '{:in [?start-date]
|
(merge-query {:query '{:in [?start-date]
|
||||||
:where [[(>= ?d ?start-date)]]}
|
:where [[(>= ?d ?start-date)]]}
|
||||||
:args [(-> query-params :start-date c/to-date)]})
|
:args [(-> query-params :start-date c/to-date)]})
|
||||||
|
|
||||||
(:end-date query-params)
|
(:end-date query-params)
|
||||||
(merge-query {:query '{:in [?end-date]
|
(merge-query {:query '{:in [?end-date]
|
||||||
:where [[(< ?d ?end-date)]]}
|
:where [[(< ?d ?end-date)]]}
|
||||||
:args [(-> query-params :end-date c/to-date)]})
|
:args [(-> query-params :end-date c/to-date)]})
|
||||||
|
|
||||||
|
|
||||||
true
|
true
|
||||||
(merge-query {:query {:find ['?sort-default '?e]
|
(merge-query {:query {:find ['?sort-default '?e]
|
||||||
:where ['[?e :sales-summary/date ?sort-default]]}}))]
|
:where ['[?e :sales-summary/date ?sort-default]]}}))]
|
||||||
(cond->> (query2 query)
|
(cond->> (query2 query)
|
||||||
true (apply-sort-3 query-params)
|
true (apply-sort-3 query-params)
|
||||||
@@ -107,38 +123,55 @@
|
|||||||
refunds))
|
refunds))
|
||||||
|
|
||||||
(defn fetch-page [request]
|
(defn fetch-page [request]
|
||||||
(let [db (dc/db conn)
|
(let [db (dc/db conn)
|
||||||
{ids-to-retrieve :ids matching-count :count} (fetch-ids db request)]
|
{ids-to-retrieve :ids matching-count :count} (fetch-ids db request)]
|
||||||
|
|
||||||
[(->> (hydrate-results ids-to-retrieve db request))
|
[(->> (hydrate-results ids-to-retrieve db request))
|
||||||
matching-count]))
|
matching-count]))
|
||||||
|
|
||||||
|
#_(defn get-debits [ss]
|
||||||
|
{:card-payments (+ (:sales-summary/total-card-payments ss 0.0)
|
||||||
|
(:sales-summary/total-card-fees ss 0.0)
|
||||||
|
(- (:sales-summary/total-card-refunds ss 0.0)))
|
||||||
|
:food-app-payments (+ (:sales-summary/total-food-app-payments ss 0.0)
|
||||||
|
(:sales-summary/total-food-app-fees ss 0.0)
|
||||||
|
(- (:sales-summary/total-food-app-refunds ss 0.0)))
|
||||||
|
:gift-card-payments (+ (:sales-summary/total-gift-card-payments ss 0.0)
|
||||||
|
(:sales-summary/total-gift-card-fees ss 0.0)
|
||||||
|
(- (:sales-summary/total-gift-card-refunds ss 0.0)))
|
||||||
|
#_#_:refunds (+ (:sales-summary/total-food-app-refunds ss 0.0)
|
||||||
|
(:sales-summary/total-card-refunds ss 0.0)
|
||||||
|
(:sales-summary/total-cash-refunds ss 0.0))
|
||||||
|
|
||||||
|
:fees (- (:sales-summary/total-card-fees ss 0.0))
|
||||||
|
:cash-payments (+ (:sales-summary/total-cash-payments ss 0.0)
|
||||||
|
(- (:sales-summary/total-cash-refunds ss 0.0)))
|
||||||
|
:total-unknown-processor-payments (:sales-summary/total-unknown-processor-payments ss 0.0)
|
||||||
|
:discounts (+ (:sales-summary/discount ss 0.0))
|
||||||
|
:returns (+ (:sales-summary/total-returns ss 0.0))})
|
||||||
(defn sort-items [ss]
|
(defn sort-items [ss]
|
||||||
(sort-by (juxt :ledger-mapped/ledger-side :sales-summary-item/sort-order :sales-summary-item/category) ss))
|
(sort-by (juxt :ledger-mapped/ledger-side :sales-summary-item/sort-order :sales-summary-item/category) ss))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defn total-debits [items]
|
(defn total-debits [items]
|
||||||
(->> items
|
(->> items
|
||||||
(filter #(= :ledger-side/debit (:ledger-mapped/ledger-side %)))
|
(filter #(= :ledger-side/debit (:ledger-mapped/ledger-side %)))
|
||||||
(map #(:ledger-mapped/amount % 0.0))
|
(map #(:ledger-mapped/amount % 0.0))
|
||||||
(reduce + 0.0)))
|
(reduce + 0.0)))
|
||||||
|
|
||||||
(defn total-credits [items]
|
(defn total-credits [items]
|
||||||
(->> items
|
(->> items
|
||||||
(filter #(= :ledger-side/credit (:ledger-mapped/ledger-side %)))
|
(filter #(= :ledger-side/credit (:ledger-mapped/ledger-side %)))
|
||||||
(map #(:ledger-mapped/amount % 0.0))
|
(map #(:ledger-mapped/amount % 0.0))
|
||||||
(reduce + 0.0)))
|
(reduce + 0.0)))
|
||||||
|
|
||||||
(defn truncate [s max-len]
|
|
||||||
(if (> (count s) max-len)
|
|
||||||
(str (subs s 0 (- max-len 3)) "...")
|
|
||||||
s))
|
|
||||||
|
|
||||||
(def grid-page
|
(def grid-page
|
||||||
(helper/build {:id "entity-table"
|
(helper/build {:id "entity-table"
|
||||||
:id-fn :db/id
|
:id-fn :db/id
|
||||||
:nav com/main-aside-nav
|
:nav com/admin-aside-nav
|
||||||
:fetch-page fetch-page
|
:fetch-page fetch-page
|
||||||
:page-specific-nav filters
|
:page-specific-nav filters
|
||||||
:query-schema query-schema
|
:query-schema query-schema
|
||||||
:row-buttons (fn [_ entity]
|
:row-buttons (fn [_ entity]
|
||||||
[(com/icon-button {:hx-get (bidi/path-for ssr-routes/only-routes
|
[(com/icon-button {:hx-get (bidi/path-for ssr-routes/only-routes
|
||||||
@@ -147,144 +180,93 @@
|
|||||||
svg/pencil)])
|
svg/pencil)])
|
||||||
:oob-render
|
:oob-render
|
||||||
(fn [request]
|
(fn [request]
|
||||||
[(assoc-in (date-range-field* request) [1 :hx-swap-oob] true)])
|
[#_(assoc-in (date-range-field {:value {:start (:start-date (:query-params request))
|
||||||
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
|
:end (:end-date (:query-params request))}
|
||||||
:company)}
|
:id "date-range"}) [1 :hx-swap-oob] true)]) ;; TODO
|
||||||
"POS"]
|
:breadcrumbs [[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||||
|
:admin)}
|
||||||
|
"Admin"]
|
||||||
|
|
||||||
[:a {:href (bidi/path-for ssr-routes/only-routes
|
[:a {:href (bidi/path-for ssr-routes/only-routes
|
||||||
::route/page)}
|
::route/page)}
|
||||||
"Sales Summaries"]]
|
"Sales Summaries"]]
|
||||||
:title "Sales Summaries"
|
:title "Sales Summaries"
|
||||||
:entity-name "Daily Summary"
|
:entity-name "Daily Summary"
|
||||||
:route ::route/table
|
:route ::route/table
|
||||||
:headers [{:key "client"
|
:headers [{:key "client"
|
||||||
:name "Client"
|
:name "Client"
|
||||||
:sort-key "client"
|
:sort-key "client"
|
||||||
:hide? (fn [args]
|
:hide? (fn [args]
|
||||||
(= (count (:clients args)) 1))
|
(= (count (:clients args)) 1))
|
||||||
:render #(-> % :sales-summary/client :client/code)}
|
:render #(-> % :sales-summary/client :client/code)}
|
||||||
|
|
||||||
{:key "date"
|
|
||||||
:name "Date"
|
|
||||||
:sort-key "date"
|
|
||||||
:render #(some-> % :sales-summary/date (atime/unparse-local atime/normal-date))}
|
|
||||||
|
|
||||||
{:key "debits"
|
{:key "date"
|
||||||
:name "Debits"
|
:name "Date"
|
||||||
:sort-key "debits"
|
:sort-key "date"
|
||||||
:class "w-64 align-top"
|
:render #(some-> % :sales-summary/date (atime/unparse-local atime/normal-date))}
|
||||||
:render (fn [ss]
|
|
||||||
(let [items (:sales-summary/items ss)
|
|
||||||
debit-items (filter #(= :ledger-side/debit (:ledger-mapped/ledger-side %)) (sort-items items))
|
|
||||||
credit-count (count (filter #(= :ledger-side/credit (:ledger-mapped/ledger-side %)) items))
|
|
||||||
total-debits (total-debits items)
|
|
||||||
total-credits (total-credits items)
|
|
||||||
delta (- total-debits total-credits)]
|
|
||||||
[:div.flex.flex-col.h-full
|
|
||||||
[:div.font-semibold.text-sm "Debits"]
|
|
||||||
[:ul.space-y-0.5.flex-grow
|
|
||||||
(for [si debit-items]
|
|
||||||
[:li.text-sm.text-gray-700
|
|
||||||
[:span.text-gray-500 (truncate (:sales-summary-item/category si) 30)]
|
|
||||||
[:span.ml-2 "→"]
|
|
||||||
[:span.ml-2.font-mono (format "$%,.2f" (:ledger-mapped/amount si))]
|
|
||||||
(when-not (:ledger-mapped/account si)
|
|
||||||
[:span.ml-2 (com/pill {:color :red} "?")])])
|
|
||||||
(for [_ (range (max 0 (- credit-count (count debit-items))))]
|
|
||||||
[:li "\u00a0"])]
|
|
||||||
[:div.border-t.mt-1.pt-1.flex.justify-between.items-center.w-full
|
|
||||||
[:span.font-semibold "Total"]
|
|
||||||
[:span.font-mono.font-semibold (format "$%,.2f" total-debits)]]
|
|
||||||
(when-not (dollars-0? delta)
|
|
||||||
[:div.text-xs.text-red-600.flex.justify-between.w-full.mt-1
|
|
||||||
[:span "Delta:"]
|
|
||||||
[:span.font-mono (format "$%,.2f" delta)]])]))}
|
|
||||||
|
|
||||||
{:key "credits"
|
|
||||||
:name "Credits"
|
|
||||||
:sort-key "credits"
|
|
||||||
:class "w-64 align-top"
|
|
||||||
:render (fn [ss]
|
|
||||||
(let [items (:sales-summary/items ss)
|
|
||||||
credit-items (filter #(= :ledger-side/credit (:ledger-mapped/ledger-side %)) (sort-items items))
|
|
||||||
debit-count (count (filter #(= :ledger-side/debit (:ledger-mapped/ledger-side %)) items))
|
|
||||||
total-debits (total-debits items)
|
|
||||||
total-credits (total-credits items)
|
|
||||||
delta (- total-credits total-debits)]
|
|
||||||
[:div.flex.flex-col.h-full
|
|
||||||
[:div.font-semibold.text-sm "Credits"]
|
|
||||||
[:ul.space-y-0.5.flex-grow
|
|
||||||
(for [si credit-items]
|
|
||||||
[:li.text-sm.text-gray-700
|
|
||||||
[:span.text-gray-500 (truncate (:sales-summary-item/category si) 30)]
|
|
||||||
[:span.ml-2 "→"]
|
|
||||||
[:span.ml-2.font-mono (format "$%,.2f" (:ledger-mapped/amount si))]
|
|
||||||
(when-not (:ledger-mapped/account si)
|
|
||||||
[:span.ml-2 (com/pill {:color :red} "?")])])
|
|
||||||
(for [_ (range (max 0 (- debit-count (count credit-items))))]
|
|
||||||
[:li "\u00a0"])]
|
|
||||||
[:div.border-t.mt-1.pt-1.flex.justify-between.items-center.w-full
|
|
||||||
[:span.font-semibold "Total"]
|
|
||||||
[:span.font-mono.font-semibold (format "$%,.2f" total-credits)]]
|
|
||||||
(when-not (dollars-0? delta)
|
|
||||||
[:div.text-xs.text-green-600.flex.justify-between.w-full.mt-1
|
|
||||||
[:span "Delta:"]
|
|
||||||
[:span.font-mono (format "$%,.2f" delta)]])]))}
|
|
||||||
|
|
||||||
{:key "balance"
|
{:key "debits"
|
||||||
:name "Balance"
|
:name "debits"
|
||||||
:sort-key "balance"
|
:sort-key "debits"
|
||||||
:class "w-24"
|
:render (fn [ss]
|
||||||
:render (fn [ss]
|
(let [total-debits (total-debits (:sales-summary/items ss))
|
||||||
(let [items (:sales-summary/items ss)
|
total-credits (total-credits (:sales-summary/items ss))]
|
||||||
total-debits (total-debits items)
|
[:ul
|
||||||
total-credits (total-credits items)
|
(for [si (sort-items (:sales-summary/items ss))
|
||||||
delta (- total-debits total-credits)
|
:when (= :ledger-side/debit (:ledger-mapped/ledger-side si))]
|
||||||
balanced? (dollars= total-debits total-credits)
|
[:li (:sales-summary-item/category si) ": " (format "$%,.2f" (:ledger-mapped/amount si))
|
||||||
missing-account? (some #(not (:ledger-mapped/account %)) items)]
|
(when-not (:ledger-mapped/account si)
|
||||||
[:div.flex.flex-col.items-center.pt-2
|
[:span.pl-4 (com/pill {:color :red}
|
||||||
(when missing-account?
|
"missing account")])]
|
||||||
[:span.text-red-600.font-bold.text-xs "Missing acct"])
|
)
|
||||||
(if balanced?
|
[:li (com/pill {:color (if (dollars= total-debits total-credits)
|
||||||
(when-not missing-account?
|
:primary
|
||||||
[:span.text-green-600.font-bold "✓ Balanced"])
|
:red)} "Total: " (format "$%,.2f" total-debits))]]))}
|
||||||
(list
|
{:key "credits"
|
||||||
[:span.text-red-600.font-mono (format "$%,.2f" (Math/abs delta))]
|
:name "credits"
|
||||||
[:span.text-xs.text-gray-500.mt-1
|
:sort-key "credits"
|
||||||
(if (> total-debits total-credits) "Debit over" "Credit over")]))]))}
|
:render (fn [ss]
|
||||||
|
(let [total-debits (total-debits (:sales-summary/items ss))
|
||||||
|
total-credits (total-credits (:sales-summary/items ss))]
|
||||||
|
[:ul
|
||||||
|
(for [si (sort-items (:sales-summary/items ss))
|
||||||
|
:when (= :ledger-side/credit (:ledger-mapped/ledger-side si))]
|
||||||
|
[:li (:sales-summary-item/category si) ": " (format "$%,.2f" (:ledger-mapped/amount si))
|
||||||
|
(when-not (:ledger-mapped/account si)
|
||||||
|
[:span.pl-4 (com/pill {:color :red}
|
||||||
|
"missing account")])])
|
||||||
|
[:li (com/pill {:color (if (dollars= total-debits total-credits)
|
||||||
|
:primary
|
||||||
|
:red)} "Total: " (format "$%,.2f" total-credits))]]))}]}))
|
||||||
|
|
||||||
{:key "links"
|
;; TODO schema cleanup
|
||||||
:name "Links"
|
;; Decide on what should be calculated as generating ledger entries, and what should be calculated
|
||||||
:show-starting "lg"
|
;; as part of the summary
|
||||||
:class "w-8"
|
;; default thought here is that the summary has more detail (e.g., line items), fees broken out by type
|
||||||
:render (fn [ss]
|
;; and aggregated into the final ledger entry
|
||||||
(let [ledger-entry (:journal-entry/original-entity ss)]
|
;; that allows customization at any level.
|
||||||
(when (seq ledger-entry)
|
;; TODO rename refunds/returns
|
||||||
(link-dropdown
|
|
||||||
[{:link (hu/url (bidi/path-for client-routes/routes :ledger)
|
|
||||||
{:exact-match-id (:db/id (first ledger-entry))})
|
|
||||||
:color :yellow
|
|
||||||
:content "Ledger entry"}]))))}]}))
|
|
||||||
|
|
||||||
(def row* (partial helper/row* grid-page))
|
(def row* (partial helper/row* grid-page))
|
||||||
(def table* (partial helper/table* grid-page))
|
(def table* (partial helper/table* grid-page))
|
||||||
|
|
||||||
(def edit-schema
|
(def edit-schema
|
||||||
[:map
|
[:map
|
||||||
[:db/id entity-id]
|
[:db/id entity-id]
|
||||||
[:sales-summary/client [:map [:db/id entity-id]]]
|
[:sales-summary/client [:map [:db/id entity-id]]]
|
||||||
[:sales-summary/items
|
[:sales-summary/items
|
||||||
[:vector {:coerce? true}
|
[:vector {:coerce? true}
|
||||||
[:and
|
[:and
|
||||||
[:map
|
[:map
|
||||||
[:db/id [:or entity-id temp-id]]
|
[:db/id [:or entity-id temp-id]]
|
||||||
[:sales-summary-item/category [:string {:decode/string strip}]]
|
[:sales-summary-item/category [:string {:decode/string strip}]]
|
||||||
[:sales-summary-item/manual? {:default false :decode/arbitrary (fn [x] (cond
|
[:sales-summary-item/manual? {:default false :decode/arbitrary (fn [x] (cond
|
||||||
(boolean? x)
|
(boolean? x)
|
||||||
x
|
x
|
||||||
(nil? x)
|
(nil? x)
|
||||||
false
|
false
|
||||||
(str/blank? x)
|
(str/blank? x)
|
||||||
false
|
false
|
||||||
:else
|
:else
|
||||||
@@ -293,10 +275,11 @@
|
|||||||
[:credit {:optional true} [:maybe money]]
|
[:credit {:optional true} [:maybe money]]
|
||||||
[:debit {:optional true} [:maybe money]]]
|
[:debit {:optional true} [:maybe money]]]
|
||||||
[:fn {:error/message "Must choose one of credit/debit"
|
[:fn {:error/message "Must choose one of credit/debit"
|
||||||
:error/path [:credit]}
|
:error/path [:credit]}
|
||||||
(fn [x]
|
(fn [x]
|
||||||
(not (and (:credit x)
|
(not (and (:credit x)
|
||||||
(:debit x))))]]]]])
|
(:debit x))))]]]] ])
|
||||||
|
|
||||||
|
|
||||||
(defn summary-total-row* [request]
|
(defn summary-total-row* [request]
|
||||||
(let [total-credits (-> request
|
(let [total-credits (-> request
|
||||||
@@ -355,21 +338,21 @@
|
|||||||
(defn- account-typeahead*
|
(defn- account-typeahead*
|
||||||
[{:keys [name value client-id]}]
|
[{:keys [name value client-id]}]
|
||||||
[:div.flex.flex-col
|
[:div.flex.flex-col
|
||||||
(com/typeahead {:name name
|
(com/typeahead {:name name
|
||||||
:placeholder "Search..."
|
:placeholder "Search..."
|
||||||
:url (hu/url (bidi/path-for ssr-routes/only-routes :account-search)
|
:url (hu/url (bidi/path-for ssr-routes/only-routes :account-search)
|
||||||
{:client-id client-id
|
{:client-id client-id
|
||||||
:purpose "invoice"})
|
:purpose "invoice"})
|
||||||
:value value
|
:value value
|
||||||
:content-fn (fn [value]
|
:content-fn (fn [value]
|
||||||
(:account/name (d-accounts/clientize (dc/pull (dc/db conn) d-accounts/default-read value)
|
(:account/name (d-accounts/clientize (dc/pull (dc/db conn) d-accounts/default-read value)
|
||||||
client-id)))})])
|
client-id)))})])
|
||||||
|
|
||||||
(defn sales-summary-item-row* [{:keys [value client-id]}]
|
(defn sales-summary-item-row* [{:keys [value client-id]}]
|
||||||
(let [manual? (fc/field-value (:sales-summary-item/manual? value))]
|
(let [manual? (fc/field-value (:sales-summary-item/manual? value))]
|
||||||
(com/data-grid-row (cond-> {:x-ref "p"
|
(com/data-grid-row (cond-> {:x-ref "p"
|
||||||
:x-data (hx/json {})}
|
:x-data (hx/json {})}
|
||||||
(fc/field-value (:new? value)) (hx/htmx-transition-appear))
|
(fc/field-value (:new? value)) (hx/htmx-transition-appear ))
|
||||||
(fc/with-field :db/id
|
(fc/with-field :db/id
|
||||||
(com/hidden {:name (fc/field-name)
|
(com/hidden {:name (fc/field-name)
|
||||||
:value (fc/field-value)}))
|
:value (fc/field-value)}))
|
||||||
@@ -381,9 +364,9 @@
|
|||||||
(fc/with-field :sales-summary-item/category
|
(fc/with-field :sales-summary-item/category
|
||||||
(if manual?
|
(if manual?
|
||||||
(com/validated-field {:errors (fc/field-errors)}
|
(com/validated-field {:errors (fc/field-errors)}
|
||||||
(com/text-input {:placeholder "Category/Explanation"
|
(com/text-input {:placeholder "Category/Explanation"
|
||||||
:name (fc/field-name)
|
:name (fc/field-name)
|
||||||
:value (fc/field-value)}))
|
:value (fc/field-value)}))
|
||||||
|
|
||||||
(list
|
(list
|
||||||
(com/hidden {:name (fc/field-name)
|
(com/hidden {:name (fc/field-name)
|
||||||
@@ -392,9 +375,9 @@
|
|||||||
(com/data-grid-cell {}
|
(com/data-grid-cell {}
|
||||||
(fc/with-field :ledger-mapped/account
|
(fc/with-field :ledger-mapped/account
|
||||||
(com/validated-field {:errors (fc/field-errors)}
|
(com/validated-field {:errors (fc/field-errors)}
|
||||||
(account-typeahead* {:value (fc/field-value)
|
(account-typeahead* {:value (fc/field-value)
|
||||||
:client-id client-id
|
:client-id client-id
|
||||||
:name (fc/field-name)}))))
|
:name (fc/field-name)}))))
|
||||||
(com/data-grid-cell {:class "text-right"}
|
(com/data-grid-cell {:class "text-right"}
|
||||||
|
|
||||||
(if manual?
|
(if manual?
|
||||||
@@ -418,7 +401,7 @@
|
|||||||
:ledger-side/credit)
|
:ledger-side/credit)
|
||||||
(format "$%,.2f" (fc/field-value (:ledger-mapped/amount value))))))
|
(format "$%,.2f" (fc/field-value (:ledger-mapped/amount value))))))
|
||||||
(com/data-grid-cell {:class "align-top"}
|
(com/data-grid-cell {:class "align-top"}
|
||||||
(when manual?
|
(when manual?
|
||||||
(com/a-icon-button {"@click.prevent.stop" "$refs.p.remove()"} svg/x))))))
|
(com/a-icon-button {"@click.prevent.stop" "$refs.p.remove()"} svg/x))))))
|
||||||
|
|
||||||
(defrecord MainStep [linear-wizard]
|
(defrecord MainStep [linear-wizard]
|
||||||
@@ -432,45 +415,46 @@
|
|||||||
[])
|
[])
|
||||||
|
|
||||||
(step-schema [_]
|
(step-schema [_]
|
||||||
(mut/select-keys (mm/form-schema linear-wizard) #{:db/id :sales-summary/items}))
|
(mut/select-keys (mm/form-schema linear-wizard) #{:db/id :sales-summary/items}))
|
||||||
|
|
||||||
(render-step
|
(render-step
|
||||||
[this {:keys [multi-form-state] :as request}]
|
[this {:keys [multi-form-state] :as request}]
|
||||||
(mm/default-render-step
|
(mm/default-render-step
|
||||||
linear-wizard this
|
linear-wizard this
|
||||||
:head [:div.p-2 "New invoice"]
|
:head [:div.p-2 "New invoice"]
|
||||||
:body (mm/default-step-body
|
:body (mm/default-step-body
|
||||||
{}
|
{}
|
||||||
[:div
|
[:div
|
||||||
(fc/with-field :db/id
|
(fc/with-field :db/id
|
||||||
(com/hidden {:name (fc/field-name)
|
(com/hidden {:name (fc/field-name)
|
||||||
:value (fc/field-value)}))
|
:value (fc/field-value)}))
|
||||||
(com/data-grid {:headers
|
(com/data-grid {:headers
|
||||||
[(com/data-grid-header {} "Category")
|
[(com/data-grid-header {} "Category")
|
||||||
(com/data-grid-header {} "Account")
|
(com/data-grid-header {} "Account")
|
||||||
(com/data-grid-header {} "Debits")
|
(com/data-grid-header {} "Debits")
|
||||||
(com/data-grid-header {} "Credits")
|
(com/data-grid-header {} "Credits")
|
||||||
(com/data-grid-header {} "")]}
|
(com/data-grid-header {} "")]}
|
||||||
(fc/with-field :sales-summary/items
|
(fc/with-field :sales-summary/items
|
||||||
(list
|
(list
|
||||||
(fc/cursor-map #(sales-summary-item-row* {:value %
|
(fc/cursor-map #(sales-summary-item-row* {:value %
|
||||||
:client-id (:db/id (:sales-summary/client (:snapshot multi-form-state)))}))
|
:client-id (:db/id (:sales-summary/client (:snapshot multi-form-state))) }))
|
||||||
(com/data-grid-new-row {:colspan 5
|
;; TODO
|
||||||
:hx-get (bidi/path-for ssr-routes/only-routes ::route/new-summary-item)
|
(com/data-grid-new-row {:colspan 5
|
||||||
:row-offset 0
|
:hx-get (bidi/path-for ssr-routes/only-routes ::route/new-summary-item)
|
||||||
:index (count (fc/field-value))
|
:row-offset 0
|
||||||
:tr-params {:hx-vals (hx/json {:client-id (:db/id (:sales-summary/client (:snapshot multi-form-state)))})}}
|
:index (count (fc/field-value))
|
||||||
"New Summary Item")))
|
:tr-params {:hx-vals (hx/json {:client-id (:db/id (:sales-summary/client (:snapshot multi-form-state)))})}} ;; TODO
|
||||||
(summary-total-row* request)
|
"New Summary Item")))
|
||||||
(unbalanced-row* request))])
|
(summary-total-row* request)
|
||||||
|
(unbalanced-row* request)) ])
|
||||||
|
|
||||||
:footer
|
:footer
|
||||||
(mm/default-step-footer linear-wizard this :validation-route ::route/edit-wizard-navigate)
|
(mm/default-step-footer linear-wizard this :validation-route ::route/edit-wizard-navigate)
|
||||||
:validation-route ::route/edit-wizard-navigate
|
:validation-route ::route/edit-wizard-navigate
|
||||||
:width-height-class "lg:w-[850px] lg:h-[900px]")))
|
:width-height-class "lg:w-[850px] lg:h-[900px]")))
|
||||||
|
|
||||||
(defn attach-ledger [i]
|
(defn attach-ledger [i]
|
||||||
(cond-> i
|
(cond-> i
|
||||||
(:credit i) (assoc :ledger-mapped/ledger-side :ledger-side/credit
|
(:credit i) (assoc :ledger-mapped/ledger-side :ledger-side/credit
|
||||||
:ledger-mapped/amount (:credit i))
|
:ledger-mapped/amount (:credit i))
|
||||||
(:debit i) (assoc :ledger-mapped/ledger-side :ledger-side/debit
|
(:debit i) (assoc :ledger-mapped/ledger-side :ledger-side/debit
|
||||||
@@ -486,8 +470,8 @@
|
|||||||
(navigate [this step-key]
|
(navigate [this step-key]
|
||||||
(assoc this :current-step step-key))
|
(assoc this :current-step step-key))
|
||||||
(get-current-step
|
(get-current-step
|
||||||
[this]
|
[this]
|
||||||
(mm/get-step this :main))
|
(mm/get-step this :main))
|
||||||
(render-wizard [this {:keys [multi-form-state] :as request}]
|
(render-wizard [this {:keys [multi-form-state] :as request}]
|
||||||
(mm/default-render-wizard
|
(mm/default-render-wizard
|
||||||
this request
|
this request
|
||||||
@@ -499,28 +483,29 @@
|
|||||||
(steps [_]
|
(steps [_]
|
||||||
[:main])
|
[:main])
|
||||||
(get-step [this step-key]
|
(get-step [this step-key]
|
||||||
(let [step-key-result (mc/parse mm/step-key-schema step-key)
|
(let [step-key-result (mc/parse mm/step-key-schema step-key)
|
||||||
[step-key-type step-key] step-key-result]
|
[step-key-type step-key] step-key-result]
|
||||||
(->MainStep this)))
|
(->MainStep this)))
|
||||||
(form-schema [_]
|
(form-schema [_]
|
||||||
edit-schema)
|
edit-schema)
|
||||||
(submit [this {:keys [multi-form-state request-method identity] :as request}]
|
(submit [this {:keys [multi-form-state request-method identity] :as request}]
|
||||||
(let [result (:snapshot multi-form-state)
|
(let [result (:snapshot multi-form-state )
|
||||||
transaction [:upsert-sales-summary {:db/id (:db/id result)
|
transaction [:upsert-entity {:db/id (:db/id result)
|
||||||
:sales-summary/items (map
|
:sales-summary/items (map
|
||||||
(fn [i]
|
(fn [i]
|
||||||
(if (:sales-summary-item/manual? i)
|
(if (:sales-summary-item/manual? i)
|
||||||
(attach-ledger i)
|
(attach-ledger i)
|
||||||
{:db/id (:db/id i)
|
{:db/id (:db/id i)
|
||||||
:ledger-mapped/account (:ledger-mapped/account i)}))
|
:ledger-mapped/account (:ledger-mapped/account i)
|
||||||
(:sales-summary/items result))}]]
|
}))
|
||||||
|
(:sales-summary/items result))}]]
|
||||||
(clojure.pprint/pprint (:sales-summary/items result))
|
(clojure.pprint/pprint (:sales-summary/items result))
|
||||||
@(dc/transact conn [transaction])
|
@(dc/transact conn [ transaction])
|
||||||
(html-response
|
(html-response
|
||||||
(row* identity (dc/pull (dc/db conn) default-read (:db/id result))
|
(row* identity (dc/pull (dc/db conn) default-read (:db/id result))
|
||||||
{:flash? true
|
{:flash? true
|
||||||
:request request})
|
:request request})
|
||||||
:headers (cond-> {"hx-trigger" "modalclose"
|
:headers (cond-> {"hx-trigger" "modalclose"
|
||||||
"hx-retarget" (format "#entity-table tr[data-id=\"%d\"]" (:db/id result))
|
"hx-retarget" (format "#entity-table tr[data-id=\"%d\"]" (:db/id result))
|
||||||
"hx-reswap" "outerHTML"})))))
|
"hx-reswap" "outerHTML"})))))
|
||||||
|
|
||||||
@@ -540,8 +525,8 @@
|
|||||||
(def key->handler
|
(def key->handler
|
||||||
(apply-middleware-to-all-handlers
|
(apply-middleware-to-all-handlers
|
||||||
(->>
|
(->>
|
||||||
{::route/page (helper/page-route grid-page)
|
{::route/page (helper/page-route grid-page)
|
||||||
::route/table (helper/table-route grid-page)
|
::route/table (helper/table-route grid-page)
|
||||||
::route/edit-wizard (-> mm/open-wizard-handler
|
::route/edit-wizard (-> mm/open-wizard-handler
|
||||||
(mm/wrap-wizard edit-wizard)
|
(mm/wrap-wizard edit-wizard)
|
||||||
(mm/wrap-init-multi-form-state initial-edit-wizard-state)
|
(mm/wrap-init-multi-form-state initial-edit-wizard-state)
|
||||||
@@ -559,13 +544,17 @@
|
|||||||
(wrap-schema-enforce :query-schema [:map
|
(wrap-schema-enforce :query-schema [:map
|
||||||
[:client-id {:optional true}
|
[:client-id {:optional true}
|
||||||
[:maybe entity-id]]]))
|
[:maybe entity-id]]]))
|
||||||
::route/edit-wizard-submit (-> mm/submit-handler
|
::route/edit-wizard-submit (-> mm/submit-handler
|
||||||
(mm/wrap-wizard edit-wizard)
|
(mm/wrap-wizard edit-wizard)
|
||||||
(mm/wrap-decode-multi-form-state))})
|
(mm/wrap-decode-multi-form-state))})
|
||||||
(fn [h]
|
(fn [h]
|
||||||
(-> h
|
(-> h
|
||||||
(wrap-copy-qp-pqp)
|
(wrap-copy-qp-pqp)
|
||||||
(wrap-apply-sort grid-page)
|
(wrap-apply-sort grid-page)
|
||||||
(wrap-merge-prior-hx)
|
(wrap-merge-prior-hx)
|
||||||
(wrap-schema-enforce :query-schema query-schema)
|
(wrap-schema-enforce :query-schema query-schema)
|
||||||
(wrap-schema-enforce :hx-schema query-schema)))))
|
(wrap-schema-enforce :hx-schema query-schema)
|
||||||
|
(wrap-admin)
|
||||||
|
(wrap-client-redirect-unauthenticated)))))
|
||||||
|
|
||||||
|
|
||||||
@@ -6,8 +6,7 @@
|
|||||||
[auto-ap.routes.admin.excel-invoices :as ei-routes]
|
[auto-ap.routes.admin.excel-invoices :as ei-routes]
|
||||||
[auto-ap.routes.admin.import-batch :as ib-routes]
|
[auto-ap.routes.admin.import-batch :as ib-routes]
|
||||||
[auto-ap.routes.admin.transaction-rules :as transaction-rules]
|
[auto-ap.routes.admin.transaction-rules :as transaction-rules]
|
||||||
[auto-ap.routes.admin.vendors :as v-routes]
|
[auto-ap.routes.admin.vendors :as v-routes]
|
||||||
[auto-ap.routes.pos.sales-summaries :as ss-routes]
|
|
||||||
[auto-ap.routes.invoice :as invoice-route]
|
[auto-ap.routes.invoice :as invoice-route]
|
||||||
[auto-ap.routes.ledger :as ledger-routes]
|
[auto-ap.routes.ledger :as ledger-routes]
|
||||||
[auto-ap.routes.outgoing-invoice :as oi-routes]
|
[auto-ap.routes.outgoing-invoice :as oi-routes]
|
||||||
@@ -91,8 +90,8 @@
|
|||||||
(#{::invoice-route/all-page ::invoice-route/unpaid-page ::invoice-route/voided-page ::invoice-route/paid-page ::oi-routes/new ::invoice-route/import-page :invoice-glimpse :invoice-glimpse-textract-invoice} (:matched-route request))
|
(#{::invoice-route/all-page ::invoice-route/unpaid-page ::invoice-route/voided-page ::invoice-route/paid-page ::oi-routes/new ::invoice-route/import-page :invoice-glimpse :invoice-glimpse-textract-invoice} (:matched-route request))
|
||||||
"invoices"
|
"invoices"
|
||||||
|
|
||||||
(#{:pos-sales :pos-expected-deposits :pos-tenders :pos-refunds :pos-cash-drawer-shifts ::ss-routes/page} (:matched-route request))
|
(#{:pos-sales :pos-expected-deposits :pos-tenders :pos-refunds :pos-cash-drawer-shifts} (:matched-route request))
|
||||||
"sales"
|
"sales"
|
||||||
(#{::payment-routes/all-page ::payment-routes/pending-page ::payment-routes/cleared-page ::payment-routes/voided-page} (:matched-route request))
|
(#{::payment-routes/all-page ::payment-routes/pending-page ::payment-routes/cleared-page ::payment-routes/voided-page} (:matched-route request))
|
||||||
"payments"
|
"payments"
|
||||||
(#{::ledger-routes/all-page ::ledger-routes/external-page ::ledger-routes/external-import-page ::ledger-routes/balance-sheet ::ledger-routes/cash-flows ::ledger-routes/profit-and-loss} (:matched-route request))
|
(#{::ledger-routes/all-page ::ledger-routes/external-page ::ledger-routes/external-import-page ::ledger-routes/balance-sheet ::ledger-routes/cash-flows ::ledger-routes/profit-and-loss} (:matched-route request))
|
||||||
@@ -208,18 +207,12 @@
|
|||||||
:hx-boost "true"}
|
:hx-boost "true"}
|
||||||
|
|
||||||
"Refunds")
|
"Refunds")
|
||||||
(menu-button- {:href (str (bidi/path-for ssr-routes/only-routes
|
|
||||||
:pos-cash-drawer-shifts)
|
|
||||||
"?date-range=week")
|
|
||||||
:active? (= :pos-cash-drawer-shifts (:matched-route request))
|
|
||||||
:hx-boost "true"}
|
|
||||||
"Cash drawer shifts")
|
|
||||||
(menu-button- {:href (str (bidi/path-for ssr-routes/only-routes
|
(menu-button- {:href (str (bidi/path-for ssr-routes/only-routes
|
||||||
::ss-routes/page)
|
:pos-cash-drawer-shifts)
|
||||||
"?date-range=week")
|
"?date-range=week")
|
||||||
:active? (= ::ss-routes/page (:matched-route request))
|
:active? (= :pos-cash-drawer-shifts (:matched-route request))
|
||||||
:hx-boost "true"}
|
:hx-boost "true"}
|
||||||
"Summaries"))))
|
"Cash drawer shifts"))))
|
||||||
|
|
||||||
(menu-button- {"@click.prevent" "if (selected == 'payments') {selected = null } else { selected = 'payments'} "
|
(menu-button- {"@click.prevent" "if (selected == 'payments') {selected = null } else { selected = 'payments'} "
|
||||||
:icon svg/payments}
|
:icon svg/payments}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
[auto-ap.ssr.admin.excel-invoice :as admin-excel-invoices]
|
[auto-ap.ssr.admin.excel-invoice :as admin-excel-invoices]
|
||||||
[auto-ap.ssr.admin.history :as history]
|
[auto-ap.ssr.admin.history :as history]
|
||||||
[auto-ap.ssr.admin.import-batch :as import-batch]
|
[auto-ap.ssr.admin.import-batch :as import-batch]
|
||||||
[auto-ap.ssr.pos.sales-summaries :as pos-sales-summaries]
|
[auto-ap.ssr.admin.sales-summaries :as admin-sales-summaries]
|
||||||
[auto-ap.ssr.admin.transaction-rules :as admin-rules]
|
[auto-ap.ssr.admin.transaction-rules :as admin-rules]
|
||||||
[auto-ap.ssr.admin.vendors :as admin-vendors]
|
[auto-ap.ssr.admin.vendors :as admin-vendors]
|
||||||
[auto-ap.ssr.auth :as auth]
|
[auto-ap.ssr.auth :as auth]
|
||||||
@@ -85,17 +85,17 @@
|
|||||||
(into company-1099/key->handler)
|
(into company-1099/key->handler)
|
||||||
(into invoice/key->handler)
|
(into invoice/key->handler)
|
||||||
(into import-batch/key->handler)
|
(into import-batch/key->handler)
|
||||||
(into pos-sales/key->handler)
|
(into pos-sales/key->handler)
|
||||||
(into pos-expected-deposits/key->handler)
|
(into pos-expected-deposits/key->handler)
|
||||||
(into pos-tenders/key->handler)
|
(into pos-tenders/key->handler)
|
||||||
(into pos-cash-drawer-shifts/key->handler)
|
(into pos-cash-drawer-shifts/key->handler)
|
||||||
(into pos-refunds/key->handler)
|
(into pos-refunds/key->handler)
|
||||||
(into pos-sales-summaries/key->handler)
|
(into users/key->handler)
|
||||||
(into users/key->handler)
|
(into admin-accounts/key->handler)
|
||||||
(into admin-accounts/key->handler)
|
(into admin-excel-invoices/key->handler)
|
||||||
(into admin-excel-invoices/key->handler)
|
(into admin/key->handler)
|
||||||
(into admin/key->handler)
|
(into admin-jobs/key->handler)
|
||||||
(into admin-jobs/key->handler)
|
(into admin-sales-summaries/key->handler)
|
||||||
(into admin-vendors/key->handler)
|
(into admin-vendors/key->handler)
|
||||||
(into admin-clients/key->handler)
|
(into admin-clients/key->handler)
|
||||||
(into admin-rules/key->handler)
|
(into admin-rules/key->handler)
|
||||||
|
|||||||
@@ -120,8 +120,7 @@
|
|||||||
(list
|
(list
|
||||||
[:div.text-2xl.font-bold.text-gray-600 (str "Balance Sheet - " (str/join ", " (map :client/name client))) ]
|
[:div.text-2xl.font-bold.text-gray-600 (str "Balance Sheet - " (str/join ", " (map :client/name client))) ]
|
||||||
(rtable/table {:widths (cond-> (into [30 ] (repeat 13 client-count))
|
(rtable/table {:widths (cond-> (into [30 ] (repeat 13 client-count))
|
||||||
(> (count date) 1) (into (repeat 13 (* 2 client-count (dec (count date)))))
|
(> (count date) 1) (into (repeat 13 (* 2 client-count (dec (count date))))))
|
||||||
(and (> client-count 1) (= (count date) 1)) (conj 13))
|
|
||||||
:investigate-url (bidi.bidi/path-for ssr-routes/only-routes ::route/investigate)
|
:investigate-url (bidi.bidi/path-for ssr-routes/only-routes ::route/investigate)
|
||||||
:table report
|
:table report
|
||||||
:warning (not-empty (str/join "\n " (filter not-empty [warning (:warning report)])))} ))))])
|
:warning (not-empty (str/join "\n " (filter not-empty [warning (:warning report)])))} ))))])
|
||||||
@@ -202,9 +201,8 @@
|
|||||||
(conj [:paragraph {:color [128 0 0] :size 9} (:warning report)])
|
(conj [:paragraph {:color [128 0 0] :size 9} (:warning report)])
|
||||||
(conj
|
(conj
|
||||||
(table->pdf report
|
(table->pdf report
|
||||||
(cond-> (into [30 ] (repeat client-count 13))
|
(cond-> (into [30 ] (repeat client-count 13))
|
||||||
(> (count date) 1) (into (repeat (* 2 client-count (dec (count date))) 13 ))
|
(> (count date) 1) (into (repeat (* 2 client-count (dec (count date))) 13 ))))))
|
||||||
(and (> client-count 1) (= (count date) 1)) (conj 13)))))
|
|
||||||
output-stream)
|
output-stream)
|
||||||
(.toByteArray output-stream)))
|
(.toByteArray output-stream)))
|
||||||
|
|
||||||
|
|||||||
@@ -798,34 +798,30 @@
|
|||||||
|
|
||||||
|
|
||||||
(defn balance-sheet-headers [pnl-data]
|
(defn balance-sheet-headers [pnl-data]
|
||||||
(let [period-count (count (:periods (:args pnl-data)))
|
(let [period-count (count (:periods (:args pnl-data)))]
|
||||||
client-ids (set (map :client-id (:data pnl-data)))
|
|
||||||
client-count (count client-ids)
|
|
||||||
show-total? (and (> client-count 1) (= 1 period-count))]
|
|
||||||
(cond-> []
|
(cond-> []
|
||||||
(> client-count 1)
|
(> (count (set (map :client-id (:data pnl-data)))) 1)
|
||||||
(conj (cond-> (into [{:value "Client"}]
|
(conj (into [{:value "Client"}]
|
||||||
(mapcat identity
|
|
||||||
(for [client client-ids]
|
|
||||||
(cond-> [{:value (str (-> pnl-data :client-codes (get client)))}]
|
|
||||||
(> period-count 1)
|
|
||||||
(into (apply concat (repeat (dec period-count) ["" ""])))))))
|
|
||||||
show-total? (conj {:value "Total" :bold true :border [:left]})))
|
|
||||||
|
|
||||||
|
(mapcat identity
|
||||||
|
(for [client (set (map :client-id (:data pnl-data))) ]
|
||||||
|
(cond-> [{:value (str (-> pnl-data :client-codes (get client)))}]
|
||||||
|
|
||||||
|
(> period-count 1)
|
||||||
|
(into (apply concat (repeat (dec period-count) ["" ""]))))))))
|
||||||
true
|
true
|
||||||
(conj (cond-> (into [{:value "Period Ending"}]
|
(conj (into [{:value "Period Ending"}]
|
||||||
(for [client client-ids
|
(for [client (set (map :client-id (:data pnl-data)))
|
||||||
[index p] (map vector (range) (:periods (:args pnl-data)))
|
[index p] (map vector (range) (:periods (:args pnl-data)))
|
||||||
:let [is-first? (= 0 index)
|
:let [is-first? (= 0 index)
|
||||||
period-date (date->str p)
|
period-date (date->str p)
|
||||||
period-headers (if (or is-first?
|
period-headers (if (or is-first?
|
||||||
(not (:include-deltas (:args pnl-data))))
|
(not (:include-deltas (:args pnl-data))))
|
||||||
[{:value period-date}]
|
[{:value period-date}]
|
||||||
[{:value period-date}
|
[{:value period-date}
|
||||||
{:value "+/-"}])]
|
{:value "+/-"}])]
|
||||||
header period-headers]
|
header period-headers]
|
||||||
header))
|
header))))))
|
||||||
show-total? (conj {:value (date->str (first (:periods (:args pnl-data)))) :border [:left]}))))))
|
|
||||||
|
|
||||||
(defn append-deltas [table]
|
(defn append-deltas [table]
|
||||||
(->> table
|
(->> table
|
||||||
@@ -894,33 +890,12 @@
|
|||||||
:rows table})))
|
:rows table})))
|
||||||
)
|
)
|
||||||
|
|
||||||
(defn add-total-border [rows]
|
|
||||||
(map (fn [row]
|
|
||||||
(let [last-idx (dec (count row))]
|
|
||||||
(map-indexed
|
|
||||||
(fn [i cell]
|
|
||||||
(if (= i last-idx)
|
|
||||||
(let [borders (or (:border cell) [])]
|
|
||||||
(assoc cell :border (conj borders :left)))
|
|
||||||
cell))
|
|
||||||
row)))
|
|
||||||
rows))
|
|
||||||
|
|
||||||
(defn summarize-balance-sheet [pnl-data]
|
(defn summarize-balance-sheet [pnl-data]
|
||||||
(let [client-ids (set (map :client-id (:data pnl-data)))
|
(let [pnl-datas (for [client-id (set (map :client-id (:data pnl-data)))
|
||||||
client-count (count client-ids)
|
p (:periods (:args pnl-data))]
|
||||||
period-count (count (:periods (:args pnl-data)))
|
(-> pnl-data
|
||||||
show-total? (and (> client-count 1) (= 1 period-count))
|
(filter-client client-id)
|
||||||
pnl-datas (for [client-id client-ids
|
(filter-period p)))]
|
||||||
p (:periods (:args pnl-data))]
|
|
||||||
(-> pnl-data
|
|
||||||
(filter-client client-id)
|
|
||||||
(filter-period p)))
|
|
||||||
total-data (when show-total?
|
|
||||||
(-> pnl-data
|
|
||||||
(filter-period (first (:periods (:args pnl-data))))
|
|
||||||
(assoc :cell-args {:bold true})))
|
|
||||||
pnl-datas (concat pnl-datas (when total-data [total-data]))]
|
|
||||||
(let [table (-> []
|
(let [table (-> []
|
||||||
(into (detail-rows pnl-datas
|
(into (detail-rows pnl-datas
|
||||||
:assets
|
:assets
|
||||||
@@ -937,11 +912,10 @@
|
|||||||
(negate #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable}))
|
(negate #{:cogs :payroll :controllable :fixed-overhead :ownership-controllable}))
|
||||||
pnl-datas)
|
pnl-datas)
|
||||||
"Retained Earnings")))
|
"Retained Earnings")))
|
||||||
table (if (and (> period-count 1)
|
table (if (and (> (count (:periods (:args pnl-data))) 1)
|
||||||
(:include-deltas (:args pnl-data)))
|
(:include-deltas (:args pnl-data)))
|
||||||
(append-deltas table)
|
(append-deltas table)
|
||||||
table)
|
table)]
|
||||||
table (if show-total? (add-total-border table) table)]
|
|
||||||
{:warning (warning-message pnl-data)
|
{:warning (warning-message pnl-data)
|
||||||
:header (balance-sheet-headers pnl-data)
|
:header (balance-sheet-headers pnl-data)
|
||||||
:rows table}))
|
:rows table}))
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
(ns auto-ap.routes.pos.sales-summaries)
|
(ns auto-ap.routes.admin.sales-summaries)
|
||||||
(def routes {"" {:get ::page
|
(def routes {"" {:get ::page
|
||||||
:put ::edit-wizard-submit}
|
:put ::edit-wizard-submit}
|
||||||
"/table" ::table
|
"/table" ::table
|
||||||
|
|
||||||
["/" [#"\d+" :db/id]] {:get ::edit-wizard }
|
["/" [#"\d+" :db/id]] {:get ::edit-wizard }
|
||||||
|
|
||||||
"/edit/navigate" ::edit-wizard-navigate
|
"/edit/navigate" ::edit-wizard-navigate
|
||||||
"/edit/sales-summary-item" ::new-summary-item})
|
"/edit/sales-summary-item" ::new-summary-item})
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
[auto-ap.routes.transactions :as t-routes]
|
[auto-ap.routes.transactions :as t-routes]
|
||||||
|
|
||||||
[auto-ap.routes.admin.clients :as ac-routes]
|
[auto-ap.routes.admin.clients :as ac-routes]
|
||||||
[auto-ap.routes.pos.sales-summaries :as ss-routes]
|
[auto-ap.routes.admin.sales-summaries :as ss-routes]
|
||||||
[auto-ap.routes.admin.transaction-rules :as tr-routes]))
|
[auto-ap.routes.admin.transaction-rules :as tr-routes]))
|
||||||
|
|
||||||
(def routes {"impersonate" :impersonate
|
(def routes {"impersonate" :impersonate
|
||||||
|
|||||||
@@ -265,8 +265,7 @@ NOTE: Please review the transactions we may have question for you here: https://
|
|||||||
[:div.notification.is-warning.is-light
|
[:div.notification.is-warning.is-light
|
||||||
(:warning report)])
|
(:warning report)])
|
||||||
[rtable/table {:widths (cond-> (into [30 ] (repeat 13 client-count))
|
[rtable/table {:widths (cond-> (into [30 ] (repeat 13 client-count))
|
||||||
(:include-comparison args) (into (repeat 13 (* 2 client-count)))
|
(:include-comparison args) (into (repeat 13 (* 2 client-count))))
|
||||||
(and (> client-count 1) (not (:include-comparison args))) (conj 13))
|
|
||||||
:click-event ::investigate-clicked
|
:click-event ::investigate-clicked
|
||||||
:table report}]]))
|
:table report}]]))
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,5 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
sudo docker run --rm -ti -v ~/dev/integreat/data/solr:/var/solr --network=bridge -p 8983:8983 679918342773.dkr.ecr.us-east-1.amazonaws.com/integreat-solr
|
sudo docker run --rm -ti -v ~/dev/integreat/data/solr:/var/solr --network=bridge -p 8983:8983 bryce-solr
|
||||||
|
#sudo podman container run --user 1000 --privileged --volume /home/notid/dev/integreat/data/solr:/var/solr -p 8983:8983 bryce-solr
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user