changes
This commit is contained in:
@@ -3,11 +3,56 @@
|
||||
# License OPL-1 (Odoo Proprietary License v1.0)
|
||||
# Part of the Fusion Plating product family.
|
||||
|
||||
import re
|
||||
|
||||
from markupsafe import Markup
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
|
||||
|
||||
def _bump_revision_label(label):
|
||||
"""Best-effort next revision after ``label``.
|
||||
|
||||
Customers use varied revision schemes (A/B/C, A1/A2, "Rev 1"/"Rev 2",
|
||||
custom strings). This helper handles the common ones; for unrecognised
|
||||
formats it returns ``label + '*'`` so the user knows they need to
|
||||
fix the label manually.
|
||||
|
||||
- 'A' → 'B' ... 'Y' → 'Z' → 'AA'
|
||||
- 'a' → 'b' (case preserved on single letter)
|
||||
- 'A1' → 'A2', 'B12' → 'B13', 'Rev 1' → 'Rev 2'
|
||||
- 'AB' → 'AC' (last letter incremented)
|
||||
- everything else → ``label + '*'``
|
||||
"""
|
||||
if not label:
|
||||
return 'A'
|
||||
label = label.strip()
|
||||
|
||||
# Trailing digits — "Rev 1" → "Rev 2", "A1" → "A2".
|
||||
# Preserve zero-padding when the original was padded ("014" → "015").
|
||||
m = re.match(r'^(.*?)(\d+)$', label)
|
||||
if m:
|
||||
prefix, digits = m.group(1), m.group(2)
|
||||
bumped = int(digits) + 1
|
||||
if digits.startswith('0') and len(str(bumped)) <= len(digits):
|
||||
return f"{prefix}{str(bumped).zfill(len(digits))}"
|
||||
return f"{prefix}{bumped}"
|
||||
|
||||
# Single letter
|
||||
if len(label) == 1 and label.isalpha():
|
||||
if label.upper() == 'Z':
|
||||
return 'AA' if label.isupper() else 'aa'
|
||||
return chr(ord(label) + 1)
|
||||
|
||||
# Multi-char ending in letter — "AB" → "AC"
|
||||
m = re.match(r'^(.*?)([A-Za-z])$', label)
|
||||
if m and m.group(2).upper() != 'Z':
|
||||
return m.group(1) + chr(ord(m.group(2)) + 1)
|
||||
|
||||
# Unknown format — caller must edit
|
||||
return label + '*'
|
||||
|
||||
|
||||
class FpPartCatalog(models.Model):
|
||||
"""Customer part library.
|
||||
|
||||
@@ -36,8 +81,12 @@ class FpPartCatalog(models.Model):
|
||||
tracking=True, domain="[('customer_rank', '>', 0)]",
|
||||
)
|
||||
part_number = fields.Char(string='Part Number', required=True, tracking=True, help="Customer's part number (e.g. VS-R392007E01).")
|
||||
revision = fields.Char(string='Revision', required=True, default='A', help='Revision letter or number (e.g. Rev: 1B).')
|
||||
revision_number = fields.Integer(string='Rev #', default=1)
|
||||
revision = fields.Char(
|
||||
string='Revision', required=True, default='A',
|
||||
help="Customer's drawing revision label. Free-text — accepts any "
|
||||
"format the customer uses (A, B, C / A1, B2 / Rev 1, Rev 2 / "
|
||||
"ECO-2024-014 etc.).",
|
||||
)
|
||||
revision_note = fields.Char(string='Revision Note', help='What changed in this revision.')
|
||||
revision_date = fields.Datetime(string='Revision Date', default=fields.Datetime.now)
|
||||
parent_part_id = fields.Many2one(
|
||||
@@ -643,21 +692,46 @@ class FpPartCatalog(models.Model):
|
||||
'target': 'current',
|
||||
}
|
||||
|
||||
def action_create_revision(self):
|
||||
"""Create a new revision of this part. Copies all data, increments revision number."""
|
||||
def action_open_revision_wizard(self):
|
||||
"""Open the interactive Create-New-Revision wizard.
|
||||
|
||||
This is what the form-header button calls. The wizard asks
|
||||
the user for the revision label, note, and optionally a new
|
||||
drawing/3D file BEFORE the new record is created — which is
|
||||
what most users want.
|
||||
|
||||
For non-interactive callers (auto-rev on 3D upload, direct
|
||||
order line bump) use ``action_create_revision`` directly.
|
||||
"""
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': _('Create New Revision'),
|
||||
'res_model': 'fp.part.revision.bump.wizard',
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
'context': {
|
||||
'default_part_id': self.id,
|
||||
'active_id': self.id,
|
||||
'active_model': 'fp.part.catalog',
|
||||
},
|
||||
}
|
||||
|
||||
def action_create_revision(self):
|
||||
"""Programmatic, non-interactive revision bump.
|
||||
|
||||
Copies the part with a best-effort label bump via
|
||||
``_bump_revision_label``. Used by code paths that don't have
|
||||
a user prompt (auto-rev when a new 3D model is uploaded on a
|
||||
quote, direct-order line bump). User-facing flows should call
|
||||
``action_open_revision_wizard`` instead.
|
||||
"""
|
||||
self.ensure_one()
|
||||
# Mark current as no longer latest
|
||||
self.is_latest_revision = False
|
||||
# Determine the root part for the chain
|
||||
root = self.parent_part_id or self
|
||||
# Find highest revision number in chain
|
||||
all_revs = self.env['fp.part.catalog'].search([
|
||||
'|', ('id', '=', root.id), ('parent_part_id', '=', root.id),
|
||||
])
|
||||
max_rev = max(all_revs.mapped('revision_number') or [0])
|
||||
new_label = _bump_revision_label(self.revision)
|
||||
new_rev = self.copy({
|
||||
'revision_number': max_rev + 1,
|
||||
'revision': f'Rev {max_rev + 1}',
|
||||
'revision': new_label,
|
||||
'revision_date': fields.Datetime.now(),
|
||||
'revision_note': False,
|
||||
'parent_part_id': root.id,
|
||||
|
||||
@@ -697,13 +697,10 @@ class FpQuoteConfigurator(models.Model):
|
||||
old_part = self.part_catalog_id
|
||||
old_part.is_latest_revision = False
|
||||
root = old_part.parent_part_id or old_part
|
||||
all_revs = self.env['fp.part.catalog'].search([
|
||||
'|', ('id', '=', root.id), ('parent_part_id', '=', root.id),
|
||||
])
|
||||
max_rev = max(all_revs.mapped('revision_number') or [0])
|
||||
from .fp_part_catalog import _bump_revision_label
|
||||
new_label = _bump_revision_label(old_part.revision)
|
||||
new_part = old_part.copy({
|
||||
'revision_number': max_rev + 1,
|
||||
'revision': f'Rev {max_rev + 1}',
|
||||
'revision': new_label,
|
||||
'revision_date': fields.Datetime.now(),
|
||||
'revision_note': f'Updated 3D model: {fname}',
|
||||
'parent_part_id': root.id,
|
||||
|
||||
Reference in New Issue
Block a user