diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5a371af5..68f83376 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,6 +27,14 @@ repos: additional_dependencies: - tomli + - repo: local + hooks: + - id: no-titled-beam + name: "Use 'BEAM' not 'Beam'" + language: pygrep + entry: '\bBeam\b' + files: '\.md$' + - repo: https://github.com/asottile/pyupgrade rev: v3.19.1 hooks: diff --git a/beam/beam/barcodes.py b/beam/beam/barcodes.py index e37ef30e..fc64b68f 100644 --- a/beam/beam/barcodes.py +++ b/beam/beam/barcodes.py @@ -28,6 +28,15 @@ def create_beam_barcode(doc, method=None): ): # TODO: refactor this to be configurable to "Products" or "sold" items that do not require handling units return + company = get_default_company() + if frappe.db.exists("BEAM Settings", {"company": company}): + settings = frappe.get_cached_doc("BEAM Settings", {"company": company}) + try: + allowed = frappe.parse_json(settings.auto_barcode_doctypes or '["Item", "Warehouse"]') + except Exception: + allowed = ["Item", "Warehouse"] + if doc.doctype not in allowed: + return if any([b for b in doc.barcodes if b.barcode_type == "Code128"]): return # move all other rows back diff --git a/beam/beam/doctype/beam_settings/beam_settings.js b/beam/beam/doctype/beam_settings/beam_settings.js index c653f4d6..281c3bf2 100644 --- a/beam/beam/doctype/beam_settings/beam_settings.js +++ b/beam/beam/doctype/beam_settings/beam_settings.js @@ -1,8 +1,72 @@ // Copyright (c) 2024, AgriTheory and contributors // For license information, please see license.txt -// frappe.ui.form.on("BEAM Settings", { -// refresh(frm) { +frappe.dom.set_style(` + .barcode-auto-generate-editor input[type="checkbox"]:not(:checked) + .label-area { + text-decoration: line-through; + color: var(--text-muted); + } +`) -// }, -// }); +frappe.ui.form.on('BEAM Settings', { + refresh(frm) { + const wrapper = $(frm.fields_dict.barcode_exclusions_html.wrapper) + wrapper.empty() + wrapper.addClass('barcode-auto-generate-editor').css({ + border: '1px solid var(--border-color)', + borderRadius: 'var(--border-radius)', + padding: 'var(--padding-md)', + }) + frm.barcode_exclusions_editor = new BEAMBarcodeAutoGenerateEditor(wrapper, frm) + }, +}) + +class BEAMBarcodeAutoGenerateEditor { + constructor(wrapper, frm) { + this.wrapper = wrapper + this.frm = frm + this.setup() + } + + get allowed() { + try { + return JSON.parse(this.frm.doc.auto_barcode_doctypes || '["Item", "Warehouse"]') + } catch { + return ['Item', 'Warehouse'] + } + } + + setup() { + this.multicheck = frappe.ui.form.make_control({ + parent: this.wrapper, + df: { + fieldname: 'auto_barcode_doctypes', + fieldtype: 'MultiCheck', + select_all: true, + columns: '15rem', + get_data: () => { + return frappe + .xcall('beam.beam.doctype.beam_settings.beam_settings.get_doctypes_with_item_barcodes') + .then(doctypes => { + const allowed = this.allowed + return doctypes.map(dt => ({ + label: __(dt), + value: dt, + checked: allowed.includes(dt), + })) + }) + }, + on_change: () => { + this.sync_json() + this.frm.dirty() + }, + }, + render_input: true, + }) + } + + sync_json() { + const checked = this.multicheck.get_checked_options() + frappe.model.set_value(this.frm.doctype, this.frm.docname, 'auto_barcode_doctypes', JSON.stringify(checked)) + } +} diff --git a/beam/beam/doctype/beam_settings/beam_settings.json b/beam/beam/doctype/beam_settings/beam_settings.json index b59c1ef0..f084e658 100644 --- a/beam/beam/doctype/beam_settings/beam_settings.json +++ b/beam/beam/doctype/beam_settings/beam_settings.json @@ -12,7 +12,11 @@ "column_break_vhpb", "qr_scale", "qr_border", - "qr_error_correct" + "qr_error_correct", + "barcode_generation_section", + "barcode_exclusions_html", + "column_break_barcode", + "auto_barcode_doctypes" ], "fields": [ { @@ -65,6 +69,27 @@ "label": "Company", "options": "Company", "unique": 1 + }, + { + "fieldname": "barcode_generation_section", + "fieldtype": "Section Break", + "label": "Barcode Generation" + }, + { + "fieldname": "barcode_exclusions_html", + "fieldtype": "HTML", + "label": "Disable Auto-Generation For" + }, + { + "fieldname": "column_break_barcode", + "fieldtype": "Column Break" + }, + { + "default": "[\"Item\", \"Warehouse\"]", + "fieldname": "auto_barcode_doctypes", + "fieldtype": "JSON", + "hidden": 1, + "label": "Auto Barcode Doctypes" } ], "links": [], diff --git a/beam/beam/doctype/beam_settings/beam_settings.py b/beam/beam/doctype/beam_settings/beam_settings.py index 23de90d8..15094670 100644 --- a/beam/beam/doctype/beam_settings/beam_settings.py +++ b/beam/beam/doctype/beam_settings/beam_settings.py @@ -15,3 +15,20 @@ def create_beam_settings(company: str) -> str: beams.company = company beams.save() return beams + + +@frappe.whitelist() +def get_doctypes_with_item_barcodes() -> list[str]: + """Return all doctypes that have a Table field with options 'Item Barcode'.""" + existing_doctypes = set(frappe.get_all("DocType", pluck="name")) + standard = frappe.get_all( + "DocField", + filters={"fieldtype": "Table", "options": "Item Barcode"}, + pluck="parent", + ) + custom = frappe.get_all( + "Custom Field", + filters={"fieldtype": "Table", "options": "Item Barcode"}, + pluck="dt", + ) + return sorted(existing_doctypes.intersection(standard + custom)) diff --git a/beam/beam/doctype/beam_settings/test_beam_settings.py b/beam/beam/doctype/beam_settings/test_beam_settings.py deleted file mode 100644 index 61700c28..00000000 --- a/beam/beam/doctype/beam_settings/test_beam_settings.py +++ /dev/null @@ -1,9 +0,0 @@ -# Copyright (c) 2024, AgriTheory and Contributors -# See license.txt - -# import frappe -from frappe.tests.utils import FrappeTestCase - - -class TestBEAMSettings(FrappeTestCase): - pass diff --git a/beam/docs/assets/beam_settings.png b/beam/docs/assets/beam_settings.png index 1ea936a3..f0716ed9 100644 Binary files a/beam/docs/assets/beam_settings.png and b/beam/docs/assets/beam_settings.png differ diff --git a/beam/docs/form.md b/beam/docs/form.md index 3c49d2ad..bb19afd0 100644 --- a/beam/docs/form.md +++ b/beam/docs/form.md @@ -4,7 +4,7 @@ For license information, please see license.txt--> # Form
- Tyler Matteson 2026-02-21 + Tyler Matteson 2026-02-23
@@ -19,7 +19,7 @@ For example, when an Item is scanned while viewing a Delivery Note record, it wi |-----------------|-----------------------|--------|--------| |Item|Delivery Note|add_or_increment|item_code| -Beam uses a [decision matrix](./matrix.md) to decide what action to take based on what kind of doctype has been scanned. +BEAM uses a [decision matrix](./matrix.md) to decide what action to take based on what kind of doctype has been scanned. Custom actions and client side functions can be added by using [hooks](./hooks.md). diff --git a/beam/docs/handling_unit.md b/beam/docs/handling_unit.md index ab498d12..8b062505 100644 --- a/beam/docs/handling_unit.md +++ b/beam/docs/handling_unit.md @@ -4,13 +4,13 @@ For license information, please see license.txt--> # Handling Unit
- Tyler Matteson 2026-02-21 + Tyler Matteson 2026-02-23
A Handling Unit is an abstraction for tracking quantities of items that are moved or stored together. It does not replace Batch or Serial numbers, the manufacture of an Item, or the functionality of the Product Bundle, but can supplement these as a way of conveniently grabbing information that would otherwise require a lot of keystrokes to enter. -By assigning a unique ID to the Handling Unit, it is possible to capture via scanner the item, net quantity, unit of measure and timestamp of the previous transaction, and then act upon that information in context, according to the [decision matrix](./matrix.md). Beam adds a new doctype, Handling Unit, to implement this functionality in ERPNext. +By assigning a unique ID to the Handling Unit, it is possible to capture via scanner the item, net quantity, unit of measure and timestamp of the previous transaction, and then act upon that information in context, according to the [decision matrix](./matrix.md). BEAM adds a new doctype, Handling Unit, to implement this functionality in ERPNext. ![Screen shot of the Handling Unit doctype listview. The list shows several new Handling Units that were created for items received via a Purchase Receipt.](./assets/handling_unit_list.png) diff --git a/beam/docs/hooks.md b/beam/docs/hooks.md index 537253b1..f3f4adb4 100644 --- a/beam/docs/hooks.md +++ b/beam/docs/hooks.md @@ -1,14 +1,14 @@ -# Extending Beam With Custom Hooks +# Extending BEAM With Custom Hooks
- Tyler Matteson 2026-02-21 + Tyler Matteson 2026-02-23
-Beam can be extended by adding configurations to your application's `hooks.py`. +BEAM can be extended by adding configurations to your application's `hooks.py`. To make scanning available on a custom doctype, add a table field for "Item Barcode" directly in the doctype or via customize form. Then add a key that is a peer with "Item" in the example below. diff --git a/beam/docs/hu_traceability_report.md b/beam/docs/hu_traceability_report.md index 9a55467d..4e47c0f0 100644 --- a/beam/docs/hu_traceability_report.md +++ b/beam/docs/hu_traceability_report.md @@ -4,7 +4,7 @@ For license information, please see license.txt--> # Handling Unit Traceability Report
- Tyler Matteson 2026-02-21 + Tyler Matteson 2026-02-23
diff --git a/beam/docs/index.md b/beam/docs/index.md index 5577d66a..6e5ff86c 100644 --- a/beam/docs/index.md +++ b/beam/docs/index.md @@ -1,18 +1,17 @@ -# Beam +# BEAM
- Tyler Matteson 2026-02-21 + Tyler Matteson 2026-02-23
- -Beam is a general purpose 2D barcode scanning application for ERPNext. +BEAM is a general purpose barcode scanning application for ERPNext. ## What does this application do? -Beam allows a user to scan a 2D barcode from either a listview or a form view, then helps enter data that would otherwise require numerous keystrokes. Unlike ERPNext's built-in barcode scanning, Beam expects the user to have a hardware barcode scanner connected to their device. +BEAM allows a user to scan a 2D or QR barcode from either a listview or a form view, then helps enter data that would otherwise require numerous keystrokes. Unlike ERPNext's built-in barcode scanning, BEAM expects the user to have a hardware barcode scanner connected to their device. For example, if the user scans a barcode associated with an Item in the Item listview, it will take them to that item's record. @@ -28,22 +27,42 @@ If the user scans an Item in a Delivery Note, it will populate everything it kno Read more about [how scanning in form views works](./form.md). -## Beam Settings +## BEAM Settings -Beam's version 15 introduced a new Beam Settings document to allow users to opt in or out of features in the app. Settings are unique on a per-company basis and are automatically generated (with default options) during certain related transactions if a Beam Settings document doesn't already exist for the company. Related transactions include submission of a Purchase Receipt, Purchase Invoice, or Stock Entry. +Version 15 introduced a new BEAM Settings document to allow users to opt in or out of features in the app. Settings are unique on a per-company basis and are automatically generated (with default options) during certain related transactions if a BEAM Settings document doesn't already exist for the company. Related transactions include submission of a Purchase Receipt, Purchase Invoice, or Stock Entry. -![Screen shot of the Beam Settings document with a field for company and a check box to enable handling units.](./assets/beam_settings.png) +![Screen shot of the BEAM Settings document showing all configuration options including barcode font size, QR code settings, and the Barcode Generation section.](./assets/beam_setings.png) Settings options include: -- **Company:** the company in ERPNext to apply the given settings to. One Beam Settings document may exist for each company in the system +- **Company:** the company in ERPNext to apply the given settings to. One BEAM Settings document may exist for each company in the system - **Enable Handling Units:** (default checked) enables the generation of Handling Units (see What is a Handling Unit section for more information) +- **Enable Scanning of Serial Numbers:** (default unchecked) when enabled, BEAM will resolve scanned barcodes against Serial Number records in addition to Item barcodes +- **Barcode Font Size:** (default 12) controls the font size of the human-readable text rendered beneath Code128 barcode images in print formats + +### QR Code Settings + +- **QR Scale:** (default 8) the module size in pixels used when generating QR code images — larger values produce a bigger image +- **QR Border:** (default 4) the quiet zone border size in modules surrounding the QR code +- **QR Error Correct:** (default M) the error correction level encoded into QR codes; options are L (7%), M (15%), Q (25%), and H (30%) — higher levels allow the code to remain scannable even if partially damaged, at the cost of a denser image + +### Barcode Generation + +The Barcode Generation section controls which document types receive an automatically generated Code128 barcode when saved. Any document type that has a Barcodes table (using the Item Barcode child doctype) is listed here. Checked items have auto-generation **enabled**; unchecked items are shown with a strikethrough and will not have barcodes generated on save. +By default, **Item** and **Warehouse** are enabled. If a Code128 barcode already exists on a document, a new one will never be generated regardless of this setting. If you customize another doctype by adding a Item Barcode table, automatic generation can be configured here but still requires a `doc_event` hook to trigger, which can be configured in your app's `hooks.py` or in a Server Script. +```python +"Asset": { + "validate": [ + "beam.beam.barcodes.create_beam_barcode", + ] +}, +``` ## What is a Handling Unit? A Handling Unit is the combination of a container, any packaging material, and the items within or on it. This could be a pallet of raw materials used in a manufacturing process, a crate containing several other Handling Units, or a delivery vehicle transporting the crates and pallets. -Handling Units have unique, scannable identification numbers that are used in any stock transaction involving the items contained within the unit. The ID allows the user to reference everything about the stock transaction, saved from previous transactions. It also enables you to track the Handling Unit throughout its life cycle. The Beam application includes a [Handling Unit Traceability report](./hu_traceability_report.md) to summarize the transactions, related documents, quantities, and warehouses that involved a given Handling Unit. +Handling Units have unique, scannable identification numbers that are used in any stock transaction involving the items contained within the unit. The ID allows the user to reference everything about the stock transaction, saved from previous transactions. It also enables you to track the Handling Unit throughout its life cycle. The BEAM application includes a [Handling Unit Traceability report](./hu_traceability_report.md) to summarize the transactions, related documents, quantities, and warehouses that involved a given Handling Unit. A Handling Unit is generated when materials are received or created in the manufacturing process. @@ -51,7 +70,7 @@ Read more [about Handling Units here](./handling_unit.md). ## Installation and Customization -Beam comes packed with features, but can be extended with custom hooks both on the server side and in the client as needed. See the following pages for detailed instructions on installing and customizing the application: +BEAM comes packed with features, but can be extended with custom hooks both on the server side and in the client as needed. See the following pages for detailed instructions on installing and customizing the application: - [Installation](https://github.com/agritheory/beam) - [Customization](./hooks.md) @@ -64,7 +83,7 @@ Warehouses may also have unique barcodes associated with them. The user can navi ## Print Server Integration -Beam offers the ability to print to raw input printers like Zebra printers directly from the browser. Also included are several debugging and example print formats. For more details about configuring this, see the [print server section](./print_server.md). +BEAM offers the ability to print to raw input printers like Zebra printers directly from the browser. Also included are several debugging and example print formats. For more details about configuring this, see the [print server section](./print_server.md). ### Zebra Printing diff --git a/beam/docs/listview.md b/beam/docs/listview.md index 7cbb0f1b..8b913928 100644 --- a/beam/docs/listview.md +++ b/beam/docs/listview.md @@ -4,7 +4,7 @@ For license information, please see license.txt--> # Listview
- Tyler Matteson 2026-02-21 + Tyler Matteson 2026-02-23
@@ -27,7 +27,7 @@ Another example: If an Item is scanned while viewing the Purchase Receipt list, |Item|Purchase Receipt|filter|item_code| -Beam uses a [decision matrix](./matrix.md) to decide what action to take based on what kind of doctype has been scanned. +BEAM uses a [decision matrix](./matrix.md) to decide what action to take based on what kind of doctype has been scanned. Custom actions and client side functions can be added by using [hooks](./hooks.md) diff --git a/beam/docs/matrix.md b/beam/docs/matrix.md index d29daf52..d8558a12 100644 --- a/beam/docs/matrix.md +++ b/beam/docs/matrix.md @@ -4,7 +4,7 @@ For license information, please see license.txt--> # Listview Actions
- Tyler Matteson 2026-02-21 + Tyler Matteson 2026-02-23
| Scanned Doctype | Listview | Action | Target | diff --git a/beam/docs/testing.md b/beam/docs/testing.md index 7d9aff4f..e26c2d05 100644 --- a/beam/docs/testing.md +++ b/beam/docs/testing.md @@ -4,7 +4,7 @@ For license information, please see license.txt--> # Testing
- Tyler Matteson 2026-02-21 + Tyler Matteson 2026-02-23
diff --git a/beam/docs/zebra_printing.md b/beam/docs/zebra_printing.md index 69559ec8..b8543ae0 100644 --- a/beam/docs/zebra_printing.md +++ b/beam/docs/zebra_printing.md @@ -4,7 +4,7 @@ For license information, please see license.txt--> # Zebra Printing
- AgriTheory and Tyler Matteson 2026-02-24 + Tyler Matteson 2026-02-24
@@ -14,16 +14,16 @@ To create a Zebra print format, you need the following documents: ### ZPL Code Generation -Currently, only three types of printable ZPL data can be generated with utilities within Beam: +Currently, only three types of printable ZPL data can be generated with utilities within BEAM: - `Text` - `Barcode` - `Label` -Beam uses the [py-zebra-zpl](https://github.com/mtking2/py-zebra-zpl) library to generate the above types, as it provides a basic interface to create ZPL code using Python objects. Please refer to the library's documentation for more information on how to use it. +BEAM uses the [py-zebra-zpl](https://github.com/mtking2/py-zebra-zpl) library to generate the above types, as it provides a basic interface to create ZPL code using Python objects. Please refer to the library's documentation for more information on how to use it. **Note:** Additional ZPL elements (like graphic fields) and commands (text mirroring, character encoding, etc.) can be developed separately and added as text directly to the ZPL Print Format. For more information, visit the [official documentation page](https://supportcommunity.zebra.com/s/article/ZPL-Command-Information-and-DetailsV2?language=en_US) or the [Labelary ZPL Programming Guide](https://labelary.com/zpl.html). -In addition, Beam exposes the following Jinja functions to be used within a Print Format: +In addition, BEAM exposes the following Jinja functions to be used within a Print Format: --- diff --git a/beam/patches/.gitkeep b/beam/patches/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/beam/tests/test_barcode_auto_generate.py b/beam/tests/test_barcode_auto_generate.py new file mode 100644 index 00000000..1115a780 --- /dev/null +++ b/beam/tests/test_barcode_auto_generate.py @@ -0,0 +1,74 @@ +# Copyright (c) 2025, AgriTheory and contributors +# For license information, please see license.txt + +import frappe +import pytest + +from beam.beam.barcodes import create_beam_barcode +from beam.beam.doctype.beam_settings.beam_settings import get_doctypes_with_item_barcodes + + +def test_get_doctypes_with_item_barcodes(): + doctypes = get_doctypes_with_item_barcodes() + assert isinstance(doctypes, list) + assert "Item" in doctypes + assert "Warehouse" in doctypes + # all returned values must be real doctypes + for dt in doctypes: + assert frappe.db.exists("DocType", dt), f"Stale DocField reference: '{dt}' does not exist" + + +def _make_item(item_code): + if frappe.db.exists("Item", item_code): + item = frappe.get_doc("Item", item_code) + item.barcodes = [] + return item + item = frappe.new_doc("Item") + item.item_code = item_code + item.item_name = item_code + item.item_group = "All Item Groups" + item.stock_uom = "Nos" + item.is_stock_item = 1 + return item + + +@pytest.fixture() +def beam_settings(): + company = frappe.defaults.get_defaults().get("company") + settings = frappe.get_doc("BEAM Settings", {"company": company}) + original = settings.auto_barcode_doctypes + yield settings + settings.auto_barcode_doctypes = original + settings.save() + + +def test_barcode_generated_when_doctype_allowed(beam_settings): + beam_settings.auto_barcode_doctypes = '["Item", "Warehouse"]' + beam_settings.save() + + item = _make_item("_Test Barcode Allow Item") + create_beam_barcode(item) + + assert any(b.barcode_type == "Code128" for b in item.barcodes) + + +def test_barcode_not_generated_when_doctype_not_allowed(beam_settings): + beam_settings.auto_barcode_doctypes = '["Warehouse"]' + beam_settings.save() + + item = _make_item("_Test Barcode Disallow Item") + create_beam_barcode(item) + + assert not any(b.barcode_type == "Code128" for b in item.barcodes) + + +def test_barcode_not_duplicated_when_code128_exists(beam_settings): + beam_settings.auto_barcode_doctypes = '["Item", "Warehouse"]' + beam_settings.save() + + item = _make_item("_Test Barcode Dedup Item") + item.append("barcodes", {"barcode": "12345678901234567890", "barcode_type": "Code128"}) + create_beam_barcode(item) + + code128_barcodes = [b for b in item.barcodes if b.barcode_type == "Code128"] + assert len(code128_barcodes) == 1