"""Printedparts plugin main class. 3D-printed parts inventory + kiosk checkout. Quantity-based consumables: one row is a KIND of part with a count, not an individually tracked asset, so unlike most plugins this one seeds NO AssetType (ADR-001 assets are one-row-per-physical-thing). See docs/proposals/printedparts-plugin.md. """ import json import logging from pathlib import Path from typing import List, Optional, Type from flask import Flask, Blueprint from shopdb.plugins.base import BasePlugin, PluginMeta from shopdb.api import db, Setting from .models import PrintedItem, PrintedItemTransaction from .api import printedparts_bp logger = logging.getLogger(__name__) class PrintedpartsPlugin(BasePlugin): """3D-printed parts inventory + kiosk checkout.""" def __init__(self): manifest_path = Path(__file__).parent / 'manifest.json' with open(manifest_path) as f: self._manifest = json.load(f) @property def meta(self) -> PluginMeta: return PluginMeta( name=self._manifest['name'], version=self._manifest['version'], description=self._manifest['description'], author=self._manifest.get('author', ''), dependencies=self._manifest.get('dependencies', []), core_version=self._manifest.get('core_version', '>=0.1.0'), api_prefix=self._manifest.get('api_prefix'), ) def get_blueprint(self) -> Optional[Blueprint]: return printedparts_bp def get_models(self) -> List[Type]: return [PrintedItem, PrintedItemTransaction] def init_app(self, app: Flask, db_instance) -> None: logger.info(f'Printedparts plugin initialized (v{self.meta.version})') def get_permissions(self) -> List: """RBAC permissions this plugin owns (seeded on install/enable).""" return [ ('printedparts.view', 'View 3D printed parts', 'printedparts'), ('printedparts.create', 'Create printed parts', 'printedparts'), ('printedparts.edit', 'Edit printed parts', 'printedparts'), ('printedparts.delete', 'Retire printed parts', 'printedparts'), ('printedparts.restock', 'Restock and adjust stock counts', 'printedparts'), ] def get_navigation_items(self) -> List[dict]: return [ { 'name': '3D Parts', 'icon': 'box', 'route': '/printedparts', 'position': 46, }, ] def on_install(self, app: Flask) -> None: with app.app_context(): self._seed_settings() logger.info('Printedparts plugin installed') def _seed_settings(self) -> None: defaults = [ ('printedparts_code_prefix', '3DP', 'string', 'Prefix for generated item codes'), ('printedparts_default_threshold', '5', 'integer', 'Default low-stock threshold for new items'), ('printedparts_unknown_badge', 'deny', 'string', 'Kiosk policy when a badge resolves to no employee: allow or deny'), ] for key, value, valuetype, description in defaults: if Setting.get(key) is None: Setting.set(key, value, valuetype=valuetype, category='printedparts', description=description) db.session.commit()