drawings: on a rev mismatch, Clerical can keep the retired rev VISUAL calls (#41)
When VISUAL calls a rev that M-Files has retired, the draft is still flagged rev_mismatch, but the drawing job now also copies the rev VISUAL calls (MFilesKit 0.1.3 DrawingLookup.revision) into drawing_revisions and visual.called. A Clerical Supervisor picks either rev on the packet: "Use latest rev N" as before, or "Use rev V as VISUAL calls it" (rev-approve with use=called), with a reason and a signature. The kept rev holds while M-Files' Effective rev stays the one chosen against: no flag, no change order. A newer Effective rev flags the packet again; a new VISUAL rev clears the choice. Migration 0009 adds drawing_revisions. Needs mfileskit 0.1.3. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -17,7 +17,7 @@ from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Protocol
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from mfileskit.drawings import DrawingStatus
|
||||
from mfileskit.drawings import DrawingStatus, RevisionFile
|
||||
from mfileskit.vault import Vault
|
||||
|
||||
from ..settings import Settings
|
||||
@@ -50,6 +50,14 @@ class Found:
|
||||
draft_where: str = ""
|
||||
in_drafting: bool = False
|
||||
note: str = ""
|
||||
# Set only on a `Lookup.revision` result: M-Files' Controlled Status for that rev ("Effective", "Retired") and
|
||||
# its workflow state ("Superseded").
|
||||
controlled: str = ""
|
||||
state: str = ""
|
||||
|
||||
@property
|
||||
def effective(self) -> bool:
|
||||
return self.controlled.casefold() == "effective"
|
||||
|
||||
@property
|
||||
def identity(self) -> tuple[int | None, int | None, int | None, int | None]:
|
||||
@@ -63,6 +71,11 @@ def failed(dwg: str, note: str) -> Found:
|
||||
class Lookup(Protocol):
|
||||
def status(self, dwg: str) -> Found: ...
|
||||
|
||||
def revision(self, dwg: str, rev: int) -> Found | None:
|
||||
"""One rev's file whatever its status (a Retired rev VISUAL still calls), with `controlled` set; None when
|
||||
M-Files has no document at that rev. Raises LookupError when M-Files can't be read."""
|
||||
...
|
||||
|
||||
def download(self, found: Found, dest: Path) -> int:
|
||||
"""Write the released PDF to `dest`; bytes written. Raises DrawingFetchError (a LookupError)."""
|
||||
...
|
||||
@@ -91,6 +104,25 @@ def from_status(st: DrawingStatus) -> Found:
|
||||
)
|
||||
|
||||
|
||||
def from_revision(rf: RevisionFile) -> Found:
|
||||
"""MFilesKit's RevisionFile -> Found, with `controlled` and `state` set."""
|
||||
ov, fi = rf.obj_ver, rf.file
|
||||
return Found(
|
||||
dwg=rf.drawing,
|
||||
ok=True,
|
||||
rev=rf.rev,
|
||||
name=rf.name,
|
||||
obj_id=ov.id if ov is not None else None,
|
||||
obj_version=ov.version if ov is not None else None,
|
||||
file_id=fi.id if fi is not None else None,
|
||||
file_version=fi.version if fi is not None else None,
|
||||
file_size=fi.size if fi is not None else None,
|
||||
base=rf.base,
|
||||
controlled=rf.status or "",
|
||||
state=rf.state or "",
|
||||
)
|
||||
|
||||
|
||||
class MFilesLookup:
|
||||
"""M-Files through `mfileskit.drawings.DrawingLookup`. Read-only."""
|
||||
|
||||
@@ -100,6 +132,7 @@ class MFilesLookup:
|
||||
self.vault = vault
|
||||
self._lookup = DrawingLookup(vault) # reads the vault schema; raises MFilesError when the vault is down
|
||||
self._seen: dict[str, DrawingStatus] = {}
|
||||
self._seen_rev: dict[tuple[str, int], RevisionFile] = {}
|
||||
|
||||
def status(self, dwg: str) -> Found:
|
||||
from mfileskit.errors import MFilesError
|
||||
@@ -112,10 +145,27 @@ class MFilesLookup:
|
||||
self._seen[dwg] = st
|
||||
return from_status(st)
|
||||
|
||||
def revision(self, dwg: str, rev: int) -> Found | None:
|
||||
from mfileskit.errors import MFilesError
|
||||
|
||||
dwg = (dwg or "").strip()
|
||||
try:
|
||||
rf = self._lookup.revision(dwg, int(rev))
|
||||
except MFilesError as e:
|
||||
raise DrawingFetchError(f"{dwg}: M-Files lookup of rev {rev} failed: {e}"[:NOTE_MAX]) from None
|
||||
if rf is None:
|
||||
return None
|
||||
self._seen_rev[(dwg, int(rev))] = rf
|
||||
return from_revision(rf)
|
||||
|
||||
def download(self, found: Found, dest: Path) -> int:
|
||||
from mfileskit.errors import MFilesError
|
||||
|
||||
st = self._seen.get(found.dwg)
|
||||
st: DrawingStatus | RevisionFile | None
|
||||
if found.controlled and found.rev is not None:
|
||||
st = self._seen_rev.get((found.dwg, found.rev))
|
||||
else:
|
||||
st = self._seen.get(found.dwg)
|
||||
if st is None or st.obj_ver is None or st.file is None:
|
||||
raise DrawingFetchError(f"{found.dwg}: no released file to download")
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user