Commit Graph

41 Commits (ad1c857c970204a06ccca6b94943087904256235)

Author SHA1 Message Date
Dustin ad1c857c97 receipt-form: Add Restaurant toggle
The _Add Receipt_ form now has a _Restaurant_ toggle.  When uploading a
receipt that creates or updates a Firefly III transaction, if the toggle
is activated, a special tag will be added to the transaction.  The
assumption is that Firefly will have a rule to automatically adjust the
destination account, category, and/or budget for the transaction if this
tag is present.  The tag is configurable and defaults to `Food & Drink`.
2025-05-08 22:25:29 -05:00
Dustin 0eb0618fd2 add_receipt: Fix editing Firefly xact notes
dustin/receipts/pipeline/head This commit looks good Details
If a new receipt is being attached to an existing Firefly transaction,
we need to update the notes field of the transaction to match what's
specified with the receipt.  This was happening correctly for
transactions that already had value in its notes field, but not for
transactions without any notes at all.  The reason for this is because
we were only updating the field inside a conditional that checked if the
existing value was not equal to the new value, but it did not account
for the case where there was no existing value at all.
2025-05-07 18:24:04 -05:00
Dustin 166d86cf58 receipt: Fix display of notes field
dustin/receipts/pipeline/head This commit looks good Details
Unlike the normal `<textarea>` element, `<sl-textarea>` uses a `value`
attribute to store the prefilled value of the field, rather than a child
text node.
2025-05-04 11:23:48 -05:00
Dustin 79d8d899bb receipt/view: Fix image sizing on moble
dustin/receipts/pipeline/head There was a failure building this commit Details
I wasn't paying attention and added a bunch of CSS rules that only apply
to destkop screen sizes that should apply to all clients.
2025-03-14 20:57:12 -05:00
Dustin 83a4ca0ad5 receipts/view: Display PDFs in iframe
PDF attachments can now be shown on the receipt view page in an
`iframe`.
2025-03-14 20:57:12 -05:00
Dustin da3d3e4c8e receipts/list: Generate thumbnails for receipts
Instead of sending the whole image file for every receipt shown on the
list page, we now generate thumbnails for them on the fly.  This
dramatically reduces the amount of bytes sent for each image, especially
very large, high-quality photographs.  It also improves support for
non-image attachments like PDFs, by rendering image previews in the
grid view instead of a broken image placeholder.

We use GraphicsMagic to do the conversion.  Its `MagickWand` API is
pretty straightforward and convenient, and it supports a plethora of
image and image-like formats.
2025-03-14 20:57:12 -05:00
Dustin e158a095d3 receipts/add: Do not query Firefly for empty ID
The `transaction` field is usually included in the form submission, even
if it is empty.  To avoid querying Firefly for an invalid ID and logging
a useless error about it, we only query if the field is non-empty.
2025-03-14 20:27:20 -05:00
Dustin f3d31a7256 receipts/add: Skip updating notes if unchanged
If the value of the `notes` form field is the same as the current value
for the same field of an existing Firefly transaction, we do not need to
update it.
2025-03-14 20:27:18 -05:00
Dustin 1393c993e9 ci: Fix defaultBranch
dustin/receipts/pipeline/head This commit looks good Details
2025-03-13 20:27:50 -05:00
Dustin 1f4899feb3 receipts: Create/update Firefly III transactions
dustin/receipts/pipeline/head This commit looks good Details
The Add Receipt form can now create or update transactions in Firefly
III in certain circumstances:

* For existing transactions, if the description, amount, or notes
  submitted on the form differ from the corresponding values in Firefly,
  the Firefly transaction will be updated with the submitted information
* For gas station transactions, since Chase does not send useful
  notifications about these, there is now an option to create an
  entirely new transaction in Firefly, using the values provided in the
  form
* Similarly for refunds and deposits, which we also do not get helpful
  notifications about, the values in the form will be used to create a
  new transaction in Firefly

This functionality should help cover most of the edge cases that
`xactmon` cannot handle.
2025-03-13 20:02:35 -05:00
Dustin 4060dea44b Add Jenkins build pipeline
dustin/receipts/pipeline/head This commit looks good Details
2025-03-12 21:09:31 -05:00
Dustin a09de49fbe receipts: Set Cache-Control header on photos
Some of the receipt photos can be quite large.  Since there's no
(direct) way to change receipt photos, we can tell the browser to cache
them for a long time to avoid having to download them every time the
list page is shown.
2025-03-11 22:18:28 -05:00
Dustin dfa5ed407b receipt-form: Prompt before leaving unfinished form
Whenever any of the fields on the page are changed, the form will be
marked as "dirty" until it is submitted.  If the form is "dirty" and the
user tries to navigate away from the page, the browser will prompt for
confirmation before leaving the page.
2025-03-11 21:05:52 -05:00
Dustin 0c088e6fc8 receipts: Attach receipt to Firefly transaction
If a specific transaction is selected on the Add Receipt form, the
uploaded receipt image will now be attached to that transaction in
Firefly.
2025-03-11 21:04:36 -05:00
Dustin 5c7225f077 Refactor backend
* Reorganizing code into more logical modules:
  - `routes` specifically for Rocket handler functions
  - `receipts` data model for receipts
  - `transactions` for Firefly transactions
* Encapsulate database operations for receipts using the repository
  pattern; move SQL queries to external files (`sqlx` can only use
  string literals or external files for queries, not variables or
  constants)
* Remove obsolete routes, templates for old transaction-focused pages
2025-03-11 20:05:41 -05:00
Dustin 16701a6313 receipt-form: Add auto-fill feature
The Add Receipt form now has a dropdown box that will automatically
populate the data fields from the properties of an existing transaction.
This will be helpful for the cases where the transaction was created
automatically, but still needs a receipt attached to it (e.g. Wal-Mart,
Target, etc. purchases and restaurants).
2025-03-11 07:51:49 -05:00
Dustin 1d5bdfe920 transactions: Add JSON response format
Rocket makes it easy to dispatch requests for the same path to different
methods based on the `Accept` request header via the `format` argument
to the request attribute.

We'll use this JSON response format to populate a dropdown on the Add
Receipt form, which can be used to automatically fill the fields for
receipt data based on an existing transaction.
2025-03-11 07:49:34 -05:00
Dustin c3c866fb8f start-firefly: Fix volume mount point 2025-03-11 07:46:52 -05:00
Dustin b67ec4d0d9 receipts: Implement DELETE
A receipt can now be deleted by clicking the little trash can icon on
its card on the receipt list page.  To make this look nice, I had to
adjust some of the CSS for that page.  Incidentally, I was able to get
the cards to be properly aligned by changing the images to be cropped
instead of scaled, via the `object-fit: cover` CSS property.
2025-03-10 18:25:58 -05:00
Dustin a475f58def js: Factor out getResponseError utility function
This function factors out the logic of extracting an error message from
a `Response` object into a reusable utility function.  This will allow
other pages to use the same logic without duplicating it.
2025-03-10 18:22:32 -05:00
Dustin 20185bdf1f Redirect / to /receipts 2025-03-09 21:09:08 -05:00
Dustin 7ab2af94ef receipt-list: Make cards bigger
On mobile, we don't want 4 cards wide.  Let's make them a static size
and let there be however many fit on a row.
2025-03-09 21:14:03 -05:00
Dustin e2b14ecf10 receipt-form: Omit empty uploaded file
If there is no file to be uploaded, then we must not send the value of
the `photo` file input.  Doing so causes two files to be uploaded, the
first one being a zero-byte file with no name, and the second one being
the one we captured from the camera.  The server only uses the first
uploaded file if there are multiple, so the correct file is never used.
2025-03-09 21:04:15 -05:00
Dustin e4ddfbd025 Run DB migrations at startup
Naturally, we need the database schema in place in order to use it.
2025-03-09 20:55:26 -05:00
Dustin da9d336817 Store receipts in the database
Rethinking the workflow again.  Requiring the transaction to be present
in Firefly already will be problematic for two very important cases:

* Gas station purchase never show up in Firefly automatically
* HSA purchase show up hours or days later and have no information
  besides the amount

These are arguably the most important cases, since they are the ones
that really need receipts in order to keep the transaction register
correct.  Thus, we need a different way to handle these types of
transactions.

Really, what I need is a way to associate transaction data with an image
I really liked the original idea of storing receipts in Paperless, but
that ended up not working out because the OCR failed miserably and thus
made it impossible to search, so finding a receipt meant looking at each
image individually.  I think, therefore, the best solution is to store
the images along with manually-entered data.

To implement this new functionality, I am using `sqlx`, a SQL toolkit
for Rust.  It's not exactly an ORM, nor does it have a dynamic query
builder like SQLAlchemy, but it does have compile-time checking of
query strings and can produce type-safe query results.  Rocket has
support for managing its connection pools as part of the server state,
so that simplifies usage quite a bit.

On the front-end, I factored out the camera image capture into an HTML
custom element, `camera-input`.  I did not update the original form to
use it, since I imagine that workflow will actually go away entirely.
2025-03-09 19:55:08 -05:00
Dustin 545baa1c36 Use Rocket's Figment for configuration
Instead of reading our own TOML file for configuration, we can hook into
Rocket's [built-in configuration system][0].  Although it doesn't matter
much right now, it may if we end up using Rocket's SQL [database
integration][1], because it uses the same mechanism.  Without making
this change, we would end up with two configuration files in that case.

[0]: https://rocket.rs/guide/v0.5/configuration/#extracting-values
[1]: https://rocket.rs/guide/v0.5/state/#databases
2025-03-09 07:07:55 -05:00
Dustin ff832df721 Add container image build script 2025-03-08 21:15:16 -06:00
Dustin 15227125b9 ui: Request higher video resolution from camera
Apparently, you get 640x480 unless you ask for more?  Need to constrain
the viewer size, though, otherwise it massively overflows the page and
makes it impossible to see what you're taking a picture of.
2025-03-08 20:41:40 -06:00
Dustin 3b586413ff ui: Remove photo cropper element
It turns out the cropper is _way_ too hard to use on mobile.  The
cropper handles are much too small for a touch screen.  Maybe I can
figure out how to improve the UX eventually, but for now, Tabitha
requested that we remove that functionality.
2025-03-08 20:20:29 -06:00
Dustin 9cef220f90 ui: Persist success notification, redirect to list
Tabitha requested that the application returns to the transaction list
after the form was submitted successfully.  Before the navigation
starts, we still want to see the info toast, though.  Since the
navigation may take a few seconds, we keep the toast open indefinitely.
2025-03-08 20:18:37 -06:00
Dustin 69b511613f ui: Disable submit button while cropping
It's not immediately clear that submitting the form while the cropper is
active will not include the image _at all_.  To make this evident, we
disable the submit button until the crop is complete.
2025-03-08 20:15:41 -06:00
Dustin 9a0674ec18 ui: Fix date field on xact form
Accidentally left a hard-coded development value here.
2025-03-08 20:14:54 -06:00
Dustin 6701579ee8 backend: Attach photo to Journal ID
Firefly attachments are related to transaction journal entries rather
than the transactions themselves.  In other words, they're attached to
splits.
2025-03-08 20:13:10 -06:00
Dustin 2050ebf088 ui: Move camera action buttons below image viewer
On mobile, the screen is too narrow to put the action buttons to the
right of the image viewer.
2025-03-08 20:12:20 -06:00
Dustin 2d7ff417fc ui: Select environment camera if possible
Apparently, Firefox/Android defaults to using the front camera, and
does not provide any native UI for selecting a different one.  We can
request the back camera by indiciting that the "environment" camera is
"ideal."
2025-03-08 20:10:50 -06:00
Dustin cdf50c159c Fix path to static resources
In production deployments, the static assets are stored in
`/usr/local/share/receipts/static`.  The working directory is
`/usr/local/share/receipts`, so using a relative path of `static` is
sufficient.  We can use the same path in development with a symlink
pointing to the `esbuild` output directory.
2025-03-08 20:08:45 -06:00
Dustin d2b93bff3b Attach receipts to Firefly transactions
And now we come to the meat of the thing: the ability to update
transactions and attach receipts.  Most of this is straightforward,
except for changing the amount of split transactions.  Hopefully, this
won't come up too often, since I can't really split transactions without
a receipt.  Just to be on the safe side, attempting to change the amount
of a split transaction will return an error.
2025-03-08 18:31:46 -06:00
Dustin 0c6f9385e6 Fetch transactions from Firefly III
This is all pretty straightforward.  The only real problem is that
the search results only contain matching transactions *splits*.  Since
transactions themselves do not have an amount, the value shown in the
_Amount_ column on the transaction list may be incorrect if a
transaction contains multiple splits and some of them do not match the
search query.
2025-03-08 16:01:08 -06:00
Dustin b55fb893e2 Implement basic page navigation w/ mock data
Obviously, we'll replace the mock `Database` with a Firefly III API
client, but this is here for now to support the UI interactions.
2025-03-08 11:16:35 -06:00
Dustin 837caecc3a js: Initial UI implementation
I've implemented the UI using TypeScript and Shoelace.  I originally
started with Pico CSS, but I didn't really like its visuals.  Since
capturing photos using the camera requires JavaScript, and that's
basically the entire point of this application, Shoelace's JavaScript
dependency (for WebComponents), is acceptable.

The photo capture uses the Media Capture Web API, which exposes the
camera directly as a video stream.  We capture a frame from this stream
and save it in a canvas, which we then pass to Cropper.js to let the
user select only the relevant portion of the picture containing the
receipt itself.
2025-03-08 11:11:42 -06:00
Dustin 9a7b083f82 Initial commit 2025-03-05 18:02:52 -06:00