feat(jobs): add fp.job native model with state machine

Header model replacing mrp.production. mail.thread for chatter,
priority/state/deadline tracking, sequence WH/JOB/00001+. Tests
cover create, confirm, cancel, and forbidden double-confirm.

State machine:
  draft -> confirmed -> in_progress -> done
              v                          ^
          cancelled              (rework reverts here)
  on_hold can be entered from confirmed or in_progress.

Step relations come in Task 1.5; SO/recipe/portal/cost extension
fields come in Task 1.4.

Part of: native job model migration (spec 2026-04-25)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-24 21:29:36 -04:00
parent 5970dfe57b
commit 26928713d5
7 changed files with 175 additions and 1 deletions

View File

@@ -1,2 +1,3 @@
# -*- coding: utf-8 -*-
from . import test_fp_work_centre
from . import test_fp_job_state_machine

View File

@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
from odoo.tests.common import TransactionCase
from odoo.exceptions import UserError
class TestFpJobStateMachine(TransactionCase):
def setUp(self):
super().setUp()
self.partner = self.env['res.partner'].create({'name': 'Test Customer'})
self.product = self.env['product.product'].create({'name': 'Widget'})
def _make_job(self, **kw):
vals = {
'partner_id': self.partner.id,
'product_id': self.product.id,
'qty': 10.0,
}
vals.update(kw)
return self.env['fp.job'].create(vals)
def test_create_lands_in_draft(self):
job = self._make_job()
self.assertEqual(job.state, 'draft')
self.assertTrue(job.name and job.name.startswith('WH/JOB/'))
def test_action_confirm_moves_to_confirmed(self):
job = self._make_job()
job.action_confirm()
self.assertEqual(job.state, 'confirmed')
def test_cannot_confirm_twice(self):
job = self._make_job()
job.action_confirm()
with self.assertRaises(UserError):
job.action_confirm()
def test_cancel_from_draft(self):
job = self._make_job()
job.action_cancel()
self.assertEqual(job.state, 'cancelled')
def test_cannot_confirm_after_cancel(self):
job = self._make_job()
job.action_cancel()
with self.assertRaises(UserError):
job.action_confirm()