container = new Container(); $this->register_services(); } /** * Retrieve the shared instance. */ public static function instance(): Plugin { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } /** * Expose the container (primarily for tests and extension points). */ public function container(): Container { return $this->container; } /** * Register core service factories. */ private function register_services(): void { $this->container->set( Logger::class, static fn(): Logger => new Logger() ); // Shared WordPress database handle. $this->container->set( \wpdb::class, static function (): \wpdb { global $wpdb; return $wpdb; } ); $this->container->set( ProductResolver::class, static fn(): ProductResolver => new ProductResolver() ); // -- Suppliers -------------------------------------------------------- $this->container->set( SupplierRepository::class, static fn( Container $c ): SupplierRepository => new SupplierRepository( $c->get( \wpdb::class ) ) ); $this->container->set( SupplierService::class, static fn( Container $c ): SupplierService => new SupplierService( $c->get( SupplierRepository::class ) ) ); // -- Product purchasing ---------------------------------------------- $this->container->set( SupplierProductRepository::class, static fn( Container $c ): SupplierProductRepository => new SupplierProductRepository( $c->get( \wpdb::class ) ) ); $this->container->set( ProductSettingsRepository::class, static fn( Container $c ): ProductSettingsRepository => new ProductSettingsRepository( $c->get( \wpdb::class ) ) ); $this->container->set( ProductPurchasingService::class, static fn( Container $c ): ProductPurchasingService => new ProductPurchasingService( $c->get( SupplierProductRepository::class ), $c->get( ProductSettingsRepository::class ), $c->get( ProductResolver::class ) ) ); // -- Costing & profit ------------------------------------------------- $this->container->set( Scheduler::class, static fn(): Scheduler => new Scheduler() ); $this->container->set( CostRecordRepository::class, static fn( Container $c ): CostRecordRepository => new CostRecordRepository( $c->get( \wpdb::class ) ) ); $this->container->set( CostSnapshotService::class, static fn( Container $c ): CostSnapshotService => new CostSnapshotService( $c->get( CostRecordRepository::class ), $c->get( ProductSettingsRepository::class ), $c->get( ProductResolver::class ), $c->get( Logger::class ) ) ); $this->container->set( ReconcileService::class, static fn( Container $c ): ReconcileService => new ReconcileService( $c->get( CostSnapshotService::class ), $c->get( Scheduler::class ), $c->get( Logger::class ) ) ); $this->container->set( ProfitService::class, static fn( Container $c ): ProfitService => new ProfitService( $c->get( CostRecordRepository::class ), $c->get( CostSnapshotService::class ), $c->get( ProductResolver::class ) ) ); $this->container->set( StockMovementRepository::class, static fn( Container $c ): StockMovementRepository => new StockMovementRepository( $c->get( \wpdb::class ) ) ); $this->container->set( CostBasisFactory::class, static fn(): CostBasisFactory => new CostBasisFactory() ); $this->container->set( StockAdjustmentService::class, static fn( Container $c ): StockAdjustmentService => new StockAdjustmentService( $c->get( StockMovementRepository::class ), $c->get( ProductSettingsRepository::class ), $c->get( ProductResolver::class ) ) ); // -- Purchase orders -------------------------------------------------- $this->container->set( PORepository::class, static fn( Container $c ): PORepository => new PORepository( $c->get( \wpdb::class ) ) ); $this->container->set( PONumberGenerator::class, static fn( Container $c ): PONumberGenerator => new PONumberGenerator( $c->get( \wpdb::class ) ) ); $this->container->set( POService::class, static fn( Container $c ): POService => new POService( $c->get( PORepository::class ), $c->get( PONumberGenerator::class ), $c->get( ProductResolver::class ), $c->get( SupplierProductRepository::class ) ) ); $this->container->set( ReceivingService::class, static fn( Container $c ): ReceivingService => new ReceivingService( $c->get( PORepository::class ), $c->get( StockMovementRepository::class ), $c->get( ProductSettingsRepository::class ), $c->get( CostBasisFactory::class ), $c->get( ProductResolver::class ), $c->get( SupplierProductRepository::class ), $c->get( Logger::class ) ) ); $this->container->set( POPrint::class, static fn( Container $c ): POPrint => new POPrint( $c->get( POService::class ), $c->get( SupplierRepository::class ) ) ); // -- Forecasting & reorder ------------------------------------------- $this->container->set( VelocityCalculator::class, static fn(): VelocityCalculator => new VelocityCalculator() ); $this->container->set( ForecastRepository::class, static fn( Container $c ): ForecastRepository => new ForecastRepository( $c->get( \wpdb::class ) ) ); $this->container->set( ForecastService::class, static fn( Container $c ): ForecastService => new ForecastService( $c->get( ForecastRepository::class ), $c->get( ProductSettingsRepository::class ), $c->get( SupplierProductRepository::class ), $c->get( CostRecordRepository::class ), $c->get( CostSnapshotService::class ), $c->get( PORepository::class ), $c->get( VelocityCalculator::class ), $c->get( ProductResolver::class ), $c->get( Logger::class ) ) ); $this->container->set( ReorderService::class, static fn( Container $c ): ReorderService => new ReorderService( $c->get( ForecastRepository::class ), $c->get( ProductSettingsRepository::class ), $c->get( SupplierProductRepository::class ), $c->get( ProductResolver::class ), $c->get( POService::class ) ) ); $this->container->set( ForecastScheduler::class, static fn( Container $c ): ForecastScheduler => new ForecastScheduler( $c->get( ForecastService::class ), $c->get( Scheduler::class ), $c->get( Logger::class ) ) ); // -- Dashboard & reports --------------------------------------------- $this->container->set( InventoryValueService::class, static fn( Container $c ): InventoryValueService => new InventoryValueService( $c->get( ProductSettingsRepository::class ), $c->get( ProductResolver::class ) ) ); $this->container->set( DashboardService::class, static fn( Container $c ): DashboardService => new DashboardService( $c->get( ProfitService::class ), $c->get( CostRecordRepository::class ), $c->get( CostSnapshotService::class ), $c->get( PORepository::class ), $c->get( ForecastRepository::class ), $c->get( ReorderService::class ), $c->get( ProductSettingsRepository::class ), $c->get( ProductResolver::class ), $c->get( SupplierRepository::class ), $c->get( InventoryValueService::class ) ) ); $this->container->set( ProductCleanup::class, static fn( Container $c ): ProductCleanup => new ProductCleanup( $c->get( ProductSettingsRepository::class ), $c->get( SupplierProductRepository::class ), $c->get( ForecastRepository::class ) ) ); $this->container->set( ReportService::class, static fn( Container $c ): ReportService => new ReportService( $c->get( ProfitService::class ), $c->get( CostRecordRepository::class ), $c->get( CostSnapshotService::class ), $c->get( PORepository::class ), $c->get( ProductSettingsRepository::class ), $c->get( ProductResolver::class ), $c->get( InventoryValueService::class ) ) ); // -- Stock notifications --------------------------------------------- $this->container->set( NotificationSettings::class, static fn(): NotificationSettings => new NotificationSettings() ); $this->container->set( NotificationService::class, static fn( Container $c ): NotificationService => new NotificationService( $c->get( NotificationSettings::class ), $c->get( Scheduler::class ), $c->get( Logger::class ) ) ); $this->container->set( NotificationTriggers::class, static fn( Container $c ): NotificationTriggers => new NotificationTriggers( $c->get( NotificationService::class ), $c->get( NotificationSettings::class ), $c->get( ReorderService::class ), $c->get( PORepository::class ), $c->get( ProductResolver::class ), $c->get( Scheduler::class ) ) ); $this->container->set( MigrationRunner::class, static fn( Container $c ): MigrationRunner => new MigrationRunner( $c->get( Logger::class ) ) ); $this->container->set( RouteRegistrar::class, static fn( Container $c ): RouteRegistrar => new RouteRegistrar( $c ) ); $this->container->set( Menu::class, static fn( Container $c ): Menu => new Menu( $c->get( AssetLoader::class ) ) ); $this->container->set( AssetLoader::class, static fn(): AssetLoader => new AssetLoader() ); } /** * Wire everything into WordPress. Safe to call once. */ public function boot(): void { if ( $this->booted ) { return; } $this->booted = true; // Translations are loaded automatically by WordPress (since 4.6) for // plugins hosted on WordPress.org, so no load_plugin_textdomain() call is // needed here. JS translations are wired via wp_set_script_translations(). // Run any pending schema migrations (covers upgrades where the version // bumped without re-activation). add_action( 'admin_init', function (): void { $runner = $this->container->get( MigrationRunner::class ); if ( $runner->needs_migration() ) { $runner->run(); } } ); // Cost snapshotting on order lifecycle events + backfill batch handler. $this->container->get( CostSnapshotService::class )->register_hooks(); $this->container->get( ReconcileService::class )->register_hooks(); // Forecast recompute schedule + batch handlers. $this->container->get( ForecastScheduler::class )->register_hooks(); // Stock alerts to email, Slack, Discord, Telegram, Teams and webhooks. $this->container->get( NotificationService::class )->register_hooks(); $this->container->get( NotificationTriggers::class )->register_hooks(); // Remove purchasing config when a product is deleted (keep history). $this->container->get( ProductCleanup::class )->register_hooks(); // REST API. add_action( 'rest_api_init', function (): void { $this->container->get( RouteRegistrar::class )->register(); } ); // Printable PO handler (admin-post endpoint; guarded by cap + nonce). add_action( 'admin_post_' . POPrint::ACTION, function (): void { $this->container->get( POPrint::class )->render(); } ); // Admin menu + assets. if ( is_admin() ) { $menu = $this->container->get( Menu::class ); add_action( 'admin_menu', array( $menu, 'register' ) ); add_action( 'admin_enqueue_scripts', array( $menu, 'maybe_enqueue_assets' ) ); } /** * Fires once the plugin has finished booting. Extension point for add-ons. * * @param Plugin $plugin The plugin instance. */ do_action( 'profit_engine_booted', $this ); } }