Skip to content

feat: add Parse button and settings dialog on Communication - #105

Open
karm1000 wants to merge 3 commits into
version-15from
feat/communication-parse-dialog
Open

karm1000 wants to merge 3 commits into
version-15from
feat/communication-parse-dialog

Conversation

@karm1000

@karm1000 karm1000 commented Oct 7, 2026 •

Copy link
Copy Markdown
Member

Summary

Adds a Parse button to the Communication form. It opens a dialog pre-filled from Transaction Parser Settings. The user can change any value and parse the selected attachments, using the same _parse_attachments job as the incoming-email hook.

Parse dialog pre-filled from settings

What the dialog pre-fills

Field Default
Transaction Party type of the sender's Party Email when Parse Party Emails is on; otherwise the transaction of the matched incoming email account
Company Company of the matched incoming email account; otherwise the user's default company
Customer / Supplier The sender's Party Email for that party type. Switches with the transaction; leave empty to let the parser identify the party
AI Model Default AI Model
Combine all attachments into one document Process One Document per Communication
Attachments Every PDF / XLSX / XLS / CSV attachment, all ticked; other file types are not listed

Page Limit starts empty. PDF Processor is not in the dialog and still comes from settings.

Switching to Expense Invoice switches the party field to Supplier

Behaviour

  • When the button shows: on saved Communications with at least one supported attachment, while the parser is enabled and the user can create a Sales Order or Purchase Invoice. It updates when attachments are added or removed, without a reload. The Transaction options list only what the user can create.
  • No matching email account: Transaction starts blank, and the party field stays hidden until one is chosen.
  • Re-parsing: the Communication is marked is_processed_by_transaction_parser as soon as parsing is queued, without changing modified. If it is already marked, the user confirms before it is parsed again, so a second click while the job is still running also asks first. Parsed files are moved to the generated document by the existing Transaction._attach_file. So the button only stays visible, and the prompt only appears, while some attachments are left unparsed.
  • Validation: parse_communication is POST-only and type-hinted. It checks read permission on the Communication, create permission on the target doctype, and that the company exists and is readable. It also checks that the party exists for the transaction's party type, and that the AI model is supported. It rejects any file that is not a supported attachment of that Communication.
  • Failure emails: these go to the user who clicked Parse.

Confirmation before parsing an already-processed communication

Changes

  • overrides/communication.py
    • The account, party and attachment matching moves into helpers shared by on_update and two new whitelisted methods: get_parse_defaults and parse_communication.
    • _parse_attachments accepts optional process_one_document and page_limit, falling back to settings as before. It now sets the processed flag with update_modified=False, so an open form doesn't hit a "modified after you opened it" error.
    • on_update still enqueues the same arguments.
  • public/js/communication.js: the button and the dialog.
  • boot.py and hooks.py: add frappe.boot.transaction_parser (enabled flag, supported extensions, party types, transactions the user can create), so the form script needs no server call or copied constants, and register the form script.
  • public/js/transaction_parser_dialog.js: exports TRANSACTION_LABEL_MAP so both dialogs use the same labels.
  • tests/test_communication.py: tests for the defaults, attachment and input validation, the per-file vs combined parse, the early processed flag, bootinfo, and regression checks for on_update.

Notes for review

frappe.boot.transaction_parser lives in bootinfo, which Frappe caches per session. After Enabled is toggled in settings, the button appears or disappears only after the user's cache is cleared or they log in again. If the button is still visible after the parser was disabled, clicking it shows "Please enable Transaction Parser Settings".

The automatic on_update hook still doesn't check is_processed_by_transaction_parser, so it can parse an email again when it is saved later. This is existing behaviour and is left for a separate change.

Testing

  • bench run-tests --app transaction_parser --module transaction_parser.tests.test_communication: 12 tests pass. The AI parser and the background queue are mocked.
  • Manual walkthrough on a local test site.
    • No real AI API calls were made: only AIParser._make_api_call was stubbed to return canned Sales Order JSON.
    • File extraction, prompt building, response parsing and document creation all ran for real.

Draft Sales Order created from the communication

Visual QA

Recorded on a local test site with test data only.

communication-parse-dialog.webm
  1. Parse button on a received email. The email has a PDF, a CSV and a .txt file attached.
  2. Dialog pre-filled from settings. Switching to Expense Invoice changes the party field to Supplier.
  3. Parse only the PDF. The CSV is unticked, and a draft Sales Order is created.
  4. Parsing again asks first. This is the confirmation for an already-processed email.
  5. Draft Sales Order. It has the customer and company from the dialog, with PO-1001.pdf attached.

🤖 Generated with Claude Code

Lets users parse a Communication's attachments on demand. The dialog is
pre-filled from Transaction Parser Settings (matched email account, party
emails, default AI model, one-document toggle) and every value can be
changed before parsing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 2/5

[Medium risk] Adds Parse button and dialog to the Communication form.

The PR still needs the earlier permission and overlapping-job findings addressed before merging.

Findings

  1. P1 Security Read-only users can change Communications ▶
  2. P1 Repeated clicks create duplicate drafts ▶

Reviews (2) · Last reviewed commit: "fix: address review feedback on Communic..." · Reviewed by Greptile

Comment on lines +209 to +212
doc = frappe.get_doc("Communication", communication)
doc.check_permission("read")

frappe.has_permission(TRANSACTION_MAP[transaction], "create", throw=True)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Read-only users can change Communications

parse_communication only requires read access to the Communication, but its job writes is_processed_by_transaction_parser with doc.db_set. A caller with read access and permission to create the target document can therefore change a Communication they cannot edit. Check write permission before queuing the job, and add a test for a read-only caller.

How this was verified: The endpoint checks only read access before queuing _parse_attachments, which directly writes the selected Communication without another permission check.

Comment on lines +233 to +238
frappe.enqueue(
"transaction_parser.transaction_parser.overrides.communication._parse_attachments",
doc=doc,
country=_get_country(company),
transaction_type=transaction,
attachments=[supported_attachments[url] for url in file_urls],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Repeated clicks create duplicate drafts

The same attachments can be queued twice before the first job finishes. After the request returns, the dialog closes and the form reloads while the attachments are still present, so the user can click Parse again. Neither the endpoint nor the worker reserves those files or checks their current attachment before parsing.

_parse finds the files by URL even after the first job moves them. Both jobs can create drafts, and the second job moves the files off the first draft. Reserve the selected files when queuing and recheck their ownership in the worker.

@coderabbitai

coderabbitai Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

📝 Walkthrough

Walkthrough

The change adds automatic and manual parsing for Communication attachments. Shared helpers handle account and party matching, attachment filtering, and country mapping. Whitelisted endpoints return parsing defaults and validate manual parsing requests before enqueueing them. The client adds a Parse dialog for saved Communications. Boot information exposes whether parsing is enabled. Tests cover defaults, parsing modes, invalid attachments, and automatic enqueue arguments.

Priority: ⬇️ Low

Merge Risk: 🟡 Moderate · up to fb878

The new Parse dialog displays attachment filenames without escaping them. A file with a crafted name could inject script into the page of a user who opens the dialog. On sites with developer mode enabled, parsing also runs inside the web request rather than in the background queue. The filename escaping should be fixed before merging.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 30 functions across 5 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely identifies the main change: a Parse button and settings dialog on Communication records.
Description check ✅ Passed The description explains the Communication parsing workflow, defaults, validation, implementation, and testing. It is directly related to the changeset.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: afea25e0-41ec-4b05-a6b6-b423ad1217d6
📥 Commits

Reviewing files that changed from the base of the PR and between f958bee and fb878e6.

📒 Files selected for processing (5)
  • transaction_parser/boot.py
  • transaction_parser/hooks.py
  • transaction_parser/public/js/communication.js
  • transaction_parser/tests/test_communication.py
  • transaction_parser/transaction_parser/overrides/communication.py

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment on lines +100 to +101
label: attachment.file_name || attachment.file_url,
value: attachment.file_url,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
# Inspect the defaults payload and the project's declared Frappe version.
rg -n -C 8 'def get_parse_defaults|file_name|attachments' transaction_parser/transaction_parser/overrides/communication.py
rg -n -C 3 'frappe|framework' pyproject.toml

Repository: resilient-tech/transaction-parser

Length of output: 8391


🏁 Script executed:

git diff --no-ext-diff --unified=15 86a6f0b514105b55279a20b8021851ece67d3a8e fb878e68f6e1c5e0a1ff06b1c4a4813c603cf952 -- transaction_parser/public/js/communication.js
printf '\\n--- current source ---\\n'
nl -ba transaction_parser/public/js/communication.js | sed -n '55,135p'
printf '\\n--- dependency declaration ---\\n'
nl -ba pyproject.toml | sed -n '40,50p'

Repository: resilient-tech/transaction-parser

Length of output: 6973


🌐 Web query:

frappe version-15 frappe/public/js/frappe/form/controls/multicheck.js option label value HTML template source

💡 Result:

For **Frappe `version-15`**, the option HTML is built in `get_checkbox_element(option)` in [`multicheck.js`](https://github.com/frappe/frappe/blob/version-15/frappe/public/js/frappe/form/controls/multicheck.js):

```html
<div class="checkbox unit-checkbox">
  <label title="${option.description || ""}" style="display: flex; align-items: center;">
    <input type="checkbox" data-unit="${option.value}" style="flex-shrink: 0;">
    <span class="label-area" data-unit="${option.value}">${option.label}</span>
  </label>
</div>
```

Each option is an object with `label` and `value`; `checked` is also used to initialize selection. The `value` goes into `data-unit` and is returned as the selected value; the `label` is displayed in the span. ([github.com](https://github.com/frappe/frappe/blob/version-15/frappe/public/js/frappe/form/controls/multicheck.js))

Citations:

- 1: https://github.com/frappe/frappe/blob/version-15/frappe/public/js/frappe/form/controls/multicheck.js

Escape attachment options before passing them to MultiCheck.

get_parse_defaults returns attachment.file_name unchanged. Frappe 15's MultiCheck inserts option labels and values into an HTML template without escaping them. A filename containing HTML can execute when the dialog opens. Escape both label and value for the template.

Comment on lines +233 to +247
frappe.enqueue(
"transaction_parser.transaction_parser.overrides.communication._parse_attachments",
doc=doc,
country=_get_country(company),
transaction_type=transaction,
attachments=[supported_attachments[url] for url in file_urls],
ai_model=ai_model or None,
user=frappe.session.user,
party=party or None,
company=company,
process_one_document=cint(process_one_document),
page_limit=cint(page_limit) or None,
queue="long",
now=frappe.conf.developer_mode,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Do not run parsing synchronously based on developer_mode.

now=frappe.conf.developer_mode makes the whole AI parsing pipeline run inside the HTTP request on any site with developer mode enabled. Developer mode can be on in staging and on-prem deployments. On those sites, large PDFs or slow AI responses can exceed the request timeout, and the user receives an error instead of a queued job. The job also uses frappe.session.user and doc inside the request transaction, so a parse failure behaves differently from the queued path. The automatic hook does not set now, so the two paths differ. Remove now. Tests already patch frappe.enqueue.

🐛 Proposed fix
         page_limit=cint(page_limit) or None,
         queue="long",
-        now=frappe.conf.developer_mode,
     )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
frappe.enqueue(
"transaction_parser.transaction_parser.overrides.communication._parse_attachments",
doc=doc,
country=_get_country(company),
transaction_type=transaction,
attachments=[supported_attachments[url] for url in file_urls],
ai_model=ai_model or None,
user=frappe.session.user,
party=party or None,
company=company,
process_one_document=cint(process_one_document),
page_limit=cint(page_limit) or None,
queue="long",
now=frappe.conf.developer_mode,
)
frappe.enqueue(
"transaction_parser.transaction_parser.overrides.communication._parse_attachments",
doc=doc,
country=_get_country(company),
transaction_type=transaction,
attachments=[supported_attachments[url] for url in file_urls],
ai_model=ai_model or None,
user=frappe.session.user,
party=party or None,
company=company,
process_one_document=cint(process_one_document),
page_limit=cint(page_limit) or None,
queue="long",
)
🧰 Tools
🪛 GitHub Actions: Linters / 0_linters.txt

[error] 233-242: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness failed: Semgrep rule frappe-semgrep-rules.rules.security.whitelisted-side-effect-on-get reports parse_communication permits GET requests while enqueuing a job, bypassing CSRF checks. Restrict the whitelisted method to POST (or another unsafe method).

🪛 GitHub Actions: Linters / linters

[error] 233-233: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness failed: Semgrep rule whitelisted-side-effect-on-get flags parse_communication: it can accept GET requests while enqueueing work outside the request rollback. Restrict the whitelist to POST (or another unsafe method).

- mark the communication processed when parsing is enqueued, so a second
  click before the job finishes asks for confirmation
- set the processed flag without touching `modified`, so an open form can
  still be saved after the job finishes
- validate company, party and AI model in parse_communication
- hide the Parse button and transaction options the user cannot create
- start with a blank transaction and hide the party field until one is
  chosen when no incoming email account matches
- show or hide the button when attachments change, without a reload
- send supported extensions, party types and creatable transactions in
  bootinfo instead of duplicating them in JS

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@karm1000

karm1000 commented Oct 7, 2026

Copy link
Copy Markdown
Member Author

@greptileai

Fixes the blocking frappe semgrep rules missing-argument-type-hint and
whitelisted-side-effect-on-get.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant