feat(receiving): technicians can count+close receivings from the tablet

ACL: grant group_fp_technician write+create on fp.receiving / line / damage.
sudo the internal sale.order x_fc_receiving_status write so a non-privileged
technician isn't blocked inside action_mark_counted / action_close.

Tests deferred to entech (local Docker unavailable this session).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-29 09:11:39 -04:00
parent b98ee8a6fb
commit d37f10f1c3
5 changed files with 73 additions and 9 deletions

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from . import test_carrier_fields
from . import test_technician_receiving_acl

View File

@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
# Copyright 2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
"""Technician can receive (count + close) from the tablet.
Spec: docs/superpowers/specs/2026-05-29-technician-receiving-shipping-tablet-design.md
"""
from odoo.tests.common import TransactionCase
from odoo.exceptions import AccessError
class TestTechnicianReceivingAcl(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.partner = cls.env['res.partner'].create({'name': 'AclCust'})
cls.product = cls.env['product.product'].create({'name': 'AclWidget'})
cls.so = cls.env['sale.order'].create({
'partner_id': cls.partner.id,
'order_line': [(0, 0, {
'product_id': cls.product.id,
'product_uom_qty': 1,
})],
})
cls.tech = cls.env['res.users'].create({
'name': 'Tech ACL',
'login': 'tech_acl_recv',
# Odoo 19: group_ids (NOT groups_id) — CLAUDE.md rule 13c.
'group_ids': [(6, 0, [
cls.env.ref('fusion_plating.group_fp_technician').id,
])],
})
def test_technician_can_count_and_close_receiving(self):
# Created as admin; the technician must be able to count + close.
rec = self.env['fp.receiving'].create({
'sale_order_id': self.so.id,
'box_count_in': 3,
})
rec_as_tech = rec.with_user(self.tech)
try:
rec_as_tech.action_mark_counted()
except AccessError as e:
self.fail("Technician blocked marking counted: %s" % e)
self.assertEqual(rec.state, 'counted')
rec_as_tech.action_close()
self.assertEqual(rec.state, 'closed')
# The SO status write inside _update_so_receiving_status must have
# gone through (it is sudo'd) — proves no AccessError on sale.order.
self.assertEqual(self.so.x_fc_receiving_status, 'received')
def test_technician_can_create_damage(self):
rec = self.env['fp.receiving'].create({'sale_order_id': self.so.id})
dmg = self.env['fp.receiving.damage'].with_user(self.tech).create({
'receiving_id': rec.id,
'description': 'scratch on flange',
})
self.assertTrue(dmg.id)