Skip to content

feat: implement open_prefilled_doc tool for unsaved document handling - #150

Open
humedi wants to merge 6 commits into
alyf-de:developfrom
humedi:pdf-to-doc
Open

humedi wants to merge 6 commits into
alyf-de:developfrom
humedi:pdf-to-doc

Conversation

@humedi

@humedi humedi commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator

open_prefilled_doc opens an unsaved form with the given field values, including child tables. The flow is: the agent reads an attached PDF or image with extract_document_data, the document planner checks the DocType with get_meta and returns an open_prefilled_doc payload, then the agent calls that tool. Nothing is saved, and insert is not used. Link and Dynamic Link fields still run fetch_from. Afterwards only explicitly set values are restored. A later change on that open form can go through frm_set_value.

The related prompt stops the agent from calling the tool with a guessed payload. A field stays missing until it is confirmed, and a field that is only valid together with another field stays missing until that other field is included:

opd

Link targets such as items, customers, and accounts are resolved with read tools and are not invented, so the payload only contains confirmed fields and existing records.

resolves #20

Simple Testing in the Browser

Fields missing from the payload keep the default. Here uom, conversion_factor, and income_account on the item stay at whatever the item link fetches:

await ask_alyfWidget.dispatchFrontendAction("open_prefilled_doc", {
  doctype: "Sales Invoice",
  doc: {
    customer: "_Test Customer",
    company: "_Test Company",
    posting_date: "2026-09-23",
    due_date: "2026-10-24",
    currency: "USD",
    selling_price_list: "Standard Selling",
    items: [
      {
        item_code: "_Test Item",
        qty: 2,
        rate: 19.5,
        item_name: "Test Camera",
      },
    ],
    taxes: [
      {
        charge_type: "On Net Total",
        account_head: "VAT 7% - _TC",
        description: "Explicit tax row",
        rate: 7,
      },
    ],
  },
});

The same fields in the payload override the default and stay in place after fetch_from:

await ask_alyfWidget.dispatchFrontendAction("open_prefilled_doc", {
  doctype: "Sales Invoice",
  doc: {
    customer: "_Test Customer",
    company: "_Test Company",
    posting_date: "2026-09-24",
    due_date: "2026-10-24",
    currency: "USD",
    selling_price_list: "Standard Selling",
    items: [
      {
        item_code: "_Test Item",
        qty: 2,
        rate: 19.5,
        item_name: "Test Camera",
        uom: "Box",
        conversion_factor: 1,
        income_account: "Sales - _TC",
      },
    ],
    taxes: [
      {
        charge_type: "On Net Total",
        account_head: "VAT 7% - _TC",
        description: "Explicit tax row",
        rate: 7,
      },
    ],
  },
});

- Added a new tool, open_prefilled_doc, to propose opening an unsaved document with pre-filled data. Updated relevant components to support this functionality, including validation and integration into the document planner. Enhanced frontend logic to handle document filling and ensure proper payload structure. Updated translations for the new feature.
- **Important**: `_normalize_any_llm_model_id` can go from `_get_document_extraction_model_config` because the provider is already passed separately and the stored value is a bare model id, so stripping a `provider:model` prefix is a no-op for real settings and would corrupt ids that legitimately contain `:` or `/`.
@greptile-apps

greptile-apps Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[Medium impact] The PR appears safe to merge based on the changes since the previous review and the current state of the previous threads.

Reviews (5) · Last reviewed commit: "fix: clear target table before populatin..." · Reviewed by Greptile

Comment thread ask_alyf/public/js/ask_alyf.bundle.js Outdated
Comment thread ask_alyf/public/js/ask_alyf.bundle.js
Comment thread ask_alyf/ask_alyf/test_code_tools.py
- Updated the `frontend_action_result` function to append a message listing omitted fields when the action is successful.
- Added a test case to verify the correct handling of omitted fields in the conversation context.
Comment thread ask_alyf/public/js/ask_alyf.bundle.js
@humedi
humedi requested a review from barredterra September 24, 2026 14:43
Comment thread ask_alyf/ask_alyf/agent.py Outdated
Comment thread ask_alyf/public/js/ask_alyf.bundle.js
Comment thread ask_alyf/public/js/ask_alyf.bundle.js
Comment thread ask_alyf/public/js/ask_alyf.bundle.js
@github-actions

Copy link
Copy Markdown

Here is a summary of the .po file changes:

  • Changed files: 9
  • Added files: 0
  • Removed files: 0
  • Files in similar change-size groups within 2% tolerance: 9
  • Added or changed translations detected: 72 across 9 file(s)

Similar Change-Size Groups

  • Around +36 / -1 lines: 9 files (ar.po, bn.po, de.po, es.po, hi.po, ja.po, pt.po, ru.po, zh_CN.po)
ar (ask_alyf/locale/ar.po) — 8 entries

ar (ask_alyf/locale/ar.po)

Status Msgid Previous Current
added Frontend action 'open_prefilled_doc' doc DocType must match. empty يجب أن يتطابق نوع DOCTYPE للمستند في إجراء الواجهة الأمامية 'open_prefilled_doc'.
added Frontend action 'open_prefilled_doc' field 'doc' must be an object. empty يجب أن يكون حقل 'doc' في إجراء الواجهة الأمامية 'open_prefilled_doc' كائناً.
added Frontend action 'open_prefilled_doc' requires a DocType. empty يتطلب إجراء الواجهة الأمامية 'open_prefilled_doc' نوع DOCTYPE.
added Left blank: {0} empty تُرك فارغاً: {0}
added Open document action needs correction. empty يحتاج إجراء فتح المستند إلى تصحيح.
added Open unsaved {0} empty فتح {0} غير محفوظ
added Opening an unsaved {0} empty فتح {0} غير محفوظ
added Prepared unsaved document. empty تم تحضير مستند غير محفوظ.
bn (ask_alyf/locale/bn.po) — 8 entries

bn (ask_alyf/locale/bn.po)

Status Msgid Previous Current
added Frontend action 'open_prefilled_doc' doc DocType must match. empty ফ্রন্টএন্ড অ্যাকশন 'open_prefilled_doc'-এর ডকুমেন্ট DocType মিলতে হবে।
added Frontend action 'open_prefilled_doc' field 'doc' must be an object. empty ফ্রন্টএন্ড অ্যাকশন 'open_prefilled_doc'-এর ফিল্ড 'doc' একটি অবজেক্ট হতে হবে।
added Frontend action 'open_prefilled_doc' requires a DocType. empty ফ্রন্টএন্ড অ্যাকশন 'open_prefilled_doc'-এর জন্য একটি DocType প্রয়োজন।
added Left blank: {0} empty ফাঁকা রাখা হয়েছে: {0}
added Open document action needs correction. empty ডকুমেন্ট খোলার অ্যাকশনের সংশোধন প্রয়োজন।
added Open unsaved {0} empty অসংরক্ষিত {0} খুলুন
added Opening an unsaved {0} empty একটি অসংরক্ষিত {0} খোলা হচ্ছে
added Prepared unsaved document. empty অসংরক্ষিত ডকুমেন্ট প্রস্তুত করা হয়েছে।
de (ask_alyf/locale/de.po) — 8 entries

de (ask_alyf/locale/de.po)

Status Msgid Previous Current
added Frontend action 'open_prefilled_doc' doc DocType must match. empty Frontend-Aktion 'open_prefilled_doc': DocType des Dokuments muss übereinstimmen.
added Frontend action 'open_prefilled_doc' field 'doc' must be an object. empty Frontend-Aktion 'open_prefilled_doc' Feld 'doc' muss ein Objekt sein.
added Frontend action 'open_prefilled_doc' requires a DocType. empty Frontend-Aktion 'open_prefilled_doc' erfordert einen DocType.
added Left blank: {0} empty Leer gelassen: {0}
added Open document action needs correction. empty Aktion zum Öffnen eines Dokuments benötigt Korrektur.
added Open unsaved {0} empty Nicht gespeichertes {0} öffnen
added Opening an unsaved {0} empty Nicht gespeichertes {0} wird geöffnet
added Prepared unsaved document. empty Nicht gespeichertes Dokument vorbereitet.
es (ask_alyf/locale/es.po) — 8 entries

es (ask_alyf/locale/es.po)

Status Msgid Previous Current
added Frontend action 'open_prefilled_doc' doc DocType must match. empty El DocType del documento de la acción de frontend 'open_prefilled_doc' debe coincidir.
added Frontend action 'open_prefilled_doc' field 'doc' must be an object. empty El campo 'doc' de la acción de frontend 'open_prefilled_doc' debe ser un objeto.
added Frontend action 'open_prefilled_doc' requires a DocType. empty La acción de frontend 'open_prefilled_doc' requiere un DocType.
added Left blank: {0} empty Dejado en blanco: {0}
added Open document action needs correction. empty La acción de abrir documento necesita corrección.
added Open unsaved {0} empty Abrir {0} sin guardar
added Opening an unsaved {0} empty Abriendo un {0} sin guardar
added Prepared unsaved document. empty Documento sin guardar preparado.
hi (ask_alyf/locale/hi.po) — 8 entries

hi (ask_alyf/locale/hi.po)

Status Msgid Previous Current
added Frontend action 'open_prefilled_doc' doc DocType must match. empty फ्रंटएंड क्रिया 'open_prefilled_doc' का दस्तावेज़ DocType मेल खाना चाहिए।
added Frontend action 'open_prefilled_doc' field 'doc' must be an object. empty फ्रंटएंड क्रिया 'open_prefilled_doc' का फ़ील्ड 'doc' एक ऑब्जेक्ट होना चाहिए।
added Frontend action 'open_prefilled_doc' requires a DocType. empty फ्रंटएंड क्रिया 'open_prefilled_doc' के लिए एक DocType आवश्यक है।
added Left blank: {0} empty खाली छोड़ दिया गया: {0}
added Open document action needs correction. empty दस्तावेज़ खोलने की क्रिया में सुधार की आवश्यकता है।
added Open unsaved {0} empty बिना सहेजा गया {0} खोलें
added Opening an unsaved {0} empty एक बिना सहेजा गया {0} खोला जा रहा है
added Prepared unsaved document. empty बिना सहेजा गया दस्तावेज़ तैयार किया गया।
ja (ask_alyf/locale/ja.po) — 8 entries

ja (ask_alyf/locale/ja.po)

Status Msgid Previous Current
added Frontend action 'open_prefilled_doc' doc DocType must match. empty フロントエンドアクション 'open_prefilled_doc' のドキュメントの DocType は一致する必要があります。
added Frontend action 'open_prefilled_doc' field 'doc' must be an object. empty フロントエンドアクション 'open_prefilled_doc' のフィールド 'doc' はオブジェクトである必要があります。
added Frontend action 'open_prefilled_doc' requires a DocType. empty フロントエンドアクション 'open_prefilled_doc' には DocType が必要です。
added Left blank: {0} empty 空のまま: {0}
added Open document action needs correction. empty ドキュメントを開くアクションを修正する必要があります。
added Open unsaved {0} empty 未保存の{0}を開く
added Opening an unsaved {0} empty 未保存の {0} を開いています
added Prepared unsaved document. empty 未保存のドキュメントを準備しました。
pt (ask_alyf/locale/pt.po) — 8 entries

pt (ask_alyf/locale/pt.po)

Status Msgid Previous Current
added Frontend action 'open_prefilled_doc' doc DocType must match. empty O DocType do documento na ação de frontend 'open_prefilled_doc' tem de corresponder.
added Frontend action 'open_prefilled_doc' field 'doc' must be an object. empty O campo 'doc' da ação de frontend 'open_prefilled_doc' deve ser um objeto.
added Frontend action 'open_prefilled_doc' requires a DocType. empty A ação de frontend 'open_prefilled_doc' requer um DocType.
added Left blank: {0} empty Deixado em branco: {0}
added Open document action needs correction. empty A ação de abrir documento precisa de correção.
added Open unsaved {0} empty Abrir {0} não guardado
added Opening an unsaved {0} empty A abrir um {0} não guardado
added Prepared unsaved document. empty Documento não guardado preparado.
ru (ask_alyf/locale/ru.po) — 8 entries

ru (ask_alyf/locale/ru.po)

Status Msgid Previous Current
added Frontend action 'open_prefilled_doc' doc DocType must match. empty Тип документа во фронтенд-действии 'open_prefilled_doc' должен совпадать.
added Frontend action 'open_prefilled_doc' field 'doc' must be an object. empty Поле 'doc' фронтенд-действия 'open_prefilled_doc' должно быть объектом.
added Frontend action 'open_prefilled_doc' requires a DocType. empty Фронтенд-действию 'open_prefilled_doc' требуется Тип документа.
added Left blank: {0} empty Оставлено пустым: {0}
added Open document action needs correction. empty Действие открытия документа требует исправления.
added Open unsaved {0} empty Открыть несохранённый {0}
added Opening an unsaved {0} empty Открытие несохранённого {0}
added Prepared unsaved document. empty Несохранённый документ подготовлен.
zh_Hans_CN (ask_alyf/locale/zh_CN.po) — 8 entries

zh_Hans_CN (ask_alyf/locale/zh_CN.po)

Status Msgid Previous Current
added Frontend action 'open_prefilled_doc' doc DocType must match. empty 前端操作 'open_prefilled_doc' 的单据类型必须一致。
added Frontend action 'open_prefilled_doc' field 'doc' must be an object. empty 前端操作 'open_prefilled_doc' 的字段 'doc' 必须是一个对象。
added Frontend action 'open_prefilled_doc' requires a DocType. empty 前端操作 'open_prefilled_doc' 需要一个单据类型。
added Left blank: {0} empty 留空:{0}
added Open document action needs correction. empty 打开文档操作需要更正。
added Open unsaved {0} empty 打开未保存的 {0}
added Opening an unsaved {0} empty 正在打开未保存的 {0}
added Prepared unsaved document. empty 已准备未保存的文档。

@github-actions

Copy link
Copy Markdown
ask_alyf/locale/main.pot — 8 added, 0 removed, 0 corrected

ask_alyf/locale/main.pot

Status Previous Current
added empty Frontend action 'open_prefilled_doc' doc DocType must match.
added empty Frontend action 'open_prefilled_doc' field 'doc' must be an object.
added empty Frontend action 'open_prefilled_doc' requires a DocType.
added empty Left blank: {0}
added empty Open document action needs correction.
added empty Open unsaved {0}
added empty Opening an unsaved {0}
added empty Prepared unsaved document.

api_key=api_key,
api_base=api_base,
model_id=_normalize_any_llm_model_id(model_setting),
model_id=model_setting,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This reverts the model ID normalization that was added on develop in #172. It is not related to open_prefilled_doc, and it leaves _normalize_any_llm_model_id defined but unused. Please move this to a separate PR (and delete the function there), or drop it from this one.

self.toolset.read_skill,
self.toolset.set_route,
self.toolset.new_doc,
self.toolset.open_prefilled_doc,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This registers open_prefilled_doc in all modes, and it runs without confirmation (AUTO_FRONTEND_ACTION_TOOLS). But the prompt says "In Ask mode, recommend switching to Agent mode for this flow", and the follow-up tools frm_set_value / frm_add_child exist only in Agent mode. Either register it only in Agent mode, or remove that sentence from the prompt.

const value = source?.[df.fieldname];
if (!hasValue(value)) continue;
values.set(df.fieldname, value);
await setField(target, df.fieldname, value, `${pathPrefix}${df.fieldname}`);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

An invalid Link value is not detected here. For a parent Link field, Frappe's validate_link_and_fetch clears the fetched fields when the link does not exist, but it does not clear the link itself, and it does not fail the request. So a guessed customer or item stays on the form, is not in omitted_fields, and the user sees the error only on save. Maybe check the validate_link_and_fetch response, or validate the links on the server before the action is proposed.

}
};

const writeBackPayload = (target) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

target[key] = value does not run any triggers. If an async trigger (for example get_item_details) changed rate after it was filled, this restores rate, but amount and the totals are not recalculated until the user edits something. Whether that happens depends on timing (waitForRequests waits at most 5 rounds).

};

let fieldInProgress = null;
const originalMsgprint = frappe.msgprint;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This replaces three Frappe globals (frappe.msgprint, frappe.request.call, frappe.throw) while the form is filled, and uses a shared fieldInProgress flag. Any unrelated request that fails during the fill marks the current field as failed. It is hard to reason about and to maintain. Can this be simpler? If not, please add a short comment for why each patch is necessary.

for (const row of rows) {
rowNumber += 1;
if (!row || typeof row !== "object" || Array.isArray(row)) continue;
const child = frappe.model.add_child(target, childDoctype, fieldname);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The PR description says Link fields still run fetch_from. That is true only for parent fields. Child rows go through grid.set_value, which does not validate links or run the generic fetch_from. ERPNext's own scripts (for example the item_code trigger) still run, so the Sales Invoice example works. But other child DocTypes do not get their fetched values.

return _("Frontend action 'new_doc' field 'route_options' must be an object.")
return None

if tool == "open_prefilled_doc":

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please add a unit test for this branch: missing DocType, doc not an object, a different doc.doctype, and a child table given as a string.

return _("Frontend action 'open_prefilled_doc' field 'doc' must be an object.")
if doc.get("doctype") not in (None, doctype):
return _("Frontend action 'open_prefilled_doc' doc DocType must match.")
return validate_table_field_shapes(doctype, doc)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Minor: validate_table_field_shapes checks only Table fields, but the JS also handles Table MultiSelect (frappe.model.table_fields).

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.

Create doc from PDF

2 participants