feat(jobs,shopfloor): smart buttons + QR scanner + NFC tank pages
Three connected operator-workflow features for entech.
A. fp.job smart buttons — count fields and action methods for sale
order, steps, deliveries, invoices, payments, quality holds,
certificates, time logs, and portal job. Each is an oe_stat_button
that drills into the matching records, mirroring the sale.order
pattern. Cross-module models are runtime-detected so the form
stays clean when bridge modules are uninstalled.
B. Reusable QR scanner OWL component (`<QrScanner/>`) wired into the
Manager Desk, Tablet Station, Plant Overview, and Process Tree
headers. Click → modal with rear-camera stream (getUserMedia) +
BarcodeDetector live decode → opens the matching fp.job form via
the action service. Falls back to a manual URL paste box on
browsers without BarcodeDetector. Works on iOS 17+ Safari and
Android Chrome. Width uses `min(420px, 92vw)` wrapped in #{} so
dart-sass passes it through verbatim instead of trying to compute
incompatible units at compile time.
C. /fp/tank/<id> public-but-auth-required tank status page for NFC
taps. Renders the tank's current step (in-progress / paused),
queued ready steps, and most recent bath chemistry log (lines
table) on a mobile-first page. URL-based so it works on iOS Safari
without the Web NFC API — the operator taps the NFC tag, the URL
opens in the default browser, the page auto-renders. New
web.assets_frontend bundle entry pulls in the design tokens +
tank_status.scss.
Manifest version bumps: jobs 19.0.5.0.0, shopfloor 19.0.16.0.0.
Tests: 44 pass (3 new smart-button assertions added).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -576,6 +576,53 @@ class TestPhase6Controllers(TransactionCase):
|
||||
self.assertEqual(step_by_node[op.id].name, 'Op1')
|
||||
|
||||
|
||||
class TestFpJobSmartButtons(TransactionCase):
|
||||
"""Feature A — verify smart-button count fields and action methods
|
||||
are wired on fp.job. Runtime-detect tests confirm the methods exist
|
||||
without requiring downstream models to be installed."""
|
||||
|
||||
def test_smart_count_fields_exist(self):
|
||||
for f in (
|
||||
'sale_order_count', 'delivery_count', 'invoice_count',
|
||||
'payment_count', 'quality_hold_count', 'certificate_count',
|
||||
'timelog_count', 'portal_job_count',
|
||||
):
|
||||
self.assertIn(f, self.env['fp.job']._fields)
|
||||
|
||||
def test_smart_action_methods_exist(self):
|
||||
Job = self.env['fp.job']
|
||||
for m in (
|
||||
'action_view_sale_order', 'action_view_steps',
|
||||
'action_view_deliveries', 'action_view_invoices',
|
||||
'action_view_payments', 'action_view_quality_holds',
|
||||
'action_view_certificates', 'action_view_timelogs',
|
||||
'action_view_portal_job',
|
||||
):
|
||||
self.assertTrue(
|
||||
hasattr(Job, m),
|
||||
'fp.job missing action method %s' % m,
|
||||
)
|
||||
|
||||
def test_smart_counts_compute_for_empty_job(self):
|
||||
partner = self.env['res.partner'].create({'name': 'C'})
|
||||
product = self.env['product.product'].create({'name': 'W'})
|
||||
job = self.env['fp.job'].create({
|
||||
'partner_id': partner.id,
|
||||
'product_id': product.id,
|
||||
'qty': 1.0,
|
||||
})
|
||||
# All counts should be 0 on a freshly-created job (no SO,
|
||||
# no delivery, no portal job, no holds, etc.)
|
||||
self.assertEqual(job.sale_order_count, 0)
|
||||
self.assertEqual(job.delivery_count, 0)
|
||||
self.assertEqual(job.invoice_count, 0)
|
||||
self.assertEqual(job.payment_count, 0)
|
||||
self.assertEqual(job.quality_hold_count, 0)
|
||||
self.assertEqual(job.certificate_count, 0)
|
||||
self.assertEqual(job.timelog_count, 0)
|
||||
self.assertEqual(job.portal_job_count, 0)
|
||||
|
||||
|
||||
class TestPhase7Migration(TransactionCase):
|
||||
"""Phase 7 — verify the migration script idempotency-key fields are
|
||||
in place and the script files are present + parse as valid Python.
|
||||
|
||||
Reference in New Issue
Block a user