Sync InvenTree part links to Bambuddy archives
This commit is contained in:
@@ -6,6 +6,8 @@ BAMBUDDY_API_KEY=replace-with-bambuddy-api-key
|
||||
|
||||
# InvenTree URL. Use root URL or /api URL; the service normalizes it.
|
||||
INVENTREE_BASE_URL=http://host.docker.internal:1337
|
||||
# Optional public/browser URL for InvenTree. Defaults to INVENTREE_BASE_URL.
|
||||
INVENTREE_WEB_URL=
|
||||
INVENTREE_TOKEN=replace-with-inventree-token
|
||||
|
||||
# Existing InvenTree IDs where printed parts should be created/stored.
|
||||
@@ -22,6 +24,8 @@ WEBHOOK_SHARED_SECRET=
|
||||
SYNC_SUCCESS_ONLY=true
|
||||
SYNC_PART_IMAGES=true
|
||||
OVERWRITE_PART_IMAGES=false
|
||||
SYNC_ARCHIVE_EXTERNAL_LINK=true
|
||||
OVERWRITE_ARCHIVE_EXTERNAL_LINK=false
|
||||
DEFAULT_STOCK_QUANTITY=1
|
||||
INVENTREE_STOCK_STATUS=10
|
||||
PART_IPN_PREFIX=BMB
|
||||
|
||||
16
README.md
16
README.md
@@ -117,6 +117,22 @@ OVERWRITE_PART_IMAGES=false
|
||||
|
||||
Set `OVERWRITE_PART_IMAGES=true` if Bambuddy thumbnails should replace existing part images.
|
||||
|
||||
## Bambuddy External Links
|
||||
|
||||
When `SYNC_ARCHIVE_EXTERNAL_LINK=true`, the service writes the InvenTree part page URL into Bambuddy archive `external_url`.
|
||||
|
||||
The default link format is:
|
||||
|
||||
```text
|
||||
<INVENTREE_WEB_URL or INVENTREE_BASE_URL>/web/part/<part_id>/
|
||||
```
|
||||
|
||||
Existing non-InvenTree external links are preserved unless:
|
||||
|
||||
```env
|
||||
OVERWRITE_ARCHIVE_EXTERNAL_LINK=true
|
||||
```
|
||||
|
||||
## Useful Endpoints
|
||||
|
||||
```text
|
||||
|
||||
@@ -54,6 +54,10 @@ class BambuddyClient:
|
||||
filename=f"bambuddy-archive-{archive_id}-thumbnail.{extension}",
|
||||
)
|
||||
|
||||
async def update_archive_external_url(self, archive_id: int, external_url: str) -> Archive:
|
||||
data = await self._request("PATCH", f"/archives/{archive_id}", json={"external_url": external_url})
|
||||
return Archive.model_validate(data)
|
||||
|
||||
async def list_archives(
|
||||
self,
|
||||
*,
|
||||
|
||||
@@ -13,6 +13,7 @@ class Settings(BaseSettings):
|
||||
bambuddy_api_key: str
|
||||
|
||||
inventree_base_url: str
|
||||
inventree_web_url: str | None = None
|
||||
inventree_token: str
|
||||
inventree_part_category_id: int
|
||||
inventree_stock_location_id: int
|
||||
@@ -23,6 +24,8 @@ class Settings(BaseSettings):
|
||||
sync_success_only: bool = True
|
||||
sync_part_images: bool = True
|
||||
overwrite_part_images: bool = False
|
||||
sync_archive_external_link: bool = True
|
||||
overwrite_archive_external_link: bool = False
|
||||
default_stock_quantity: Annotated[float, Field(gt=0)] = 1
|
||||
inventree_stock_status: int = 10
|
||||
part_ipn_prefix: str = "BMB"
|
||||
@@ -33,9 +36,11 @@ class Settings(BaseSettings):
|
||||
http_timeout_seconds: Annotated[int, Field(ge=1)] = 30
|
||||
data_dir: Path = Path("/data")
|
||||
|
||||
@field_validator("bambuddy_base_url", "inventree_base_url")
|
||||
@field_validator("bambuddy_base_url", "inventree_base_url", "inventree_web_url")
|
||||
@classmethod
|
||||
def strip_url(cls, value: str) -> str:
|
||||
def strip_url(cls, value: str | None) -> str | None:
|
||||
if value is None or not value.strip():
|
||||
return None
|
||||
return value.rstrip("/")
|
||||
|
||||
@field_validator("part_ipn_prefix")
|
||||
@@ -53,6 +58,13 @@ class Settings(BaseSettings):
|
||||
fields = [field.strip() for field in self.part_key_fields.split(",") if field.strip()]
|
||||
return fields or ["filename", "name"]
|
||||
|
||||
@property
|
||||
def inventree_browser_url(self) -> str:
|
||||
base_url = (self.inventree_web_url or self.inventree_base_url).rstrip("/")
|
||||
if base_url.endswith("/api"):
|
||||
return base_url[:-4]
|
||||
return base_url
|
||||
|
||||
|
||||
@lru_cache
|
||||
def get_settings() -> Settings:
|
||||
|
||||
@@ -27,6 +27,7 @@ class Archive(BaseModel):
|
||||
quantity: float | None = None
|
||||
object_count: int | None = None
|
||||
cost: float | None = None
|
||||
external_url: str | None = None
|
||||
notes: str | None = None
|
||||
tags: list[str] | None = None
|
||||
|
||||
|
||||
@@ -133,6 +133,7 @@ class ArchiveSyncService:
|
||||
part_id = await self._get_or_create_part(part_key=part_key, ipn=ipn, name=name, description=description)
|
||||
await self._sync_part_parameters(part_id=part_id, archive=archive)
|
||||
await self._sync_part_image(part_id=part_id, archive=archive)
|
||||
await self._sync_archive_external_link(part_id=part_id, archive=archive)
|
||||
batch = self.inventree.batch_for_archive(archive.id)
|
||||
existing_stock = await self.inventree.find_stock_by_batch(part_id=part_id, batch=batch)
|
||||
|
||||
@@ -228,6 +229,23 @@ class ArchiveSyncService:
|
||||
)
|
||||
logger.info("Synced Bambuddy archive %s thumbnail to InvenTree part %s", archive.id, part_id)
|
||||
|
||||
async def _sync_archive_external_link(self, *, part_id: int, archive: Archive) -> None:
|
||||
if not self.settings.sync_archive_external_link:
|
||||
return
|
||||
|
||||
part_url = self.inventree_part_url(part_id)
|
||||
if archive.external_url and not self.settings.overwrite_archive_external_link:
|
||||
if archive.external_url == part_url:
|
||||
return
|
||||
if not archive.external_url.startswith(self.settings.inventree_browser_url):
|
||||
return
|
||||
|
||||
await self.bambuddy.update_archive_external_url(archive.id, part_url)
|
||||
logger.info("Synced InvenTree part %s link to Bambuddy archive %s", part_id, archive.id)
|
||||
|
||||
def inventree_part_url(self, part_id: int) -> str:
|
||||
return f"{self.settings.inventree_browser_url}/web/part/{part_id}/"
|
||||
|
||||
def part_parameters_for_archive(self, archive: Archive) -> list[dict[str, str | float | None]]:
|
||||
parameters: list[dict[str, str | float | None]] = []
|
||||
|
||||
|
||||
Reference in New Issue
Block a user