303 lines
12 KiB
PHP
303 lines
12 KiB
PHP
<?php
|
|
|
|
namespace Modules\Connector\Http\Controllers\Api\V2;
|
|
|
|
use App\Services\Api\BusinessContextService;
|
|
use App\Utils\ModuleUtil;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Modules\QualityManagement\Models\NcrReport;
|
|
use Modules\QualityManagement\Services\NcrWorkflowService;
|
|
use Modules\QualityManagement\Services\QmsIntegrationService;
|
|
use Modules\ShopFloor\Models\OeeSnapshot;
|
|
use Modules\ShopFloor\Models\ProductionEvent;
|
|
use Modules\ShopFloor\Models\WorkCenter;
|
|
use Modules\ShopFloor\Services\OeeCalculationService;
|
|
use Modules\ShopFloor\Services\ProductionEventService;
|
|
use Modules\SupplyChain\Models\RfqRequest;
|
|
use Modules\SupplyChain\Models\RfqResponse;
|
|
use Modules\SupplyChain\Services\RfqService;
|
|
|
|
class EnterpriseModulesController extends BaseController
|
|
{
|
|
public function __construct(
|
|
protected BusinessContextService $contextService,
|
|
protected ModuleUtil $moduleUtil
|
|
) {
|
|
}
|
|
|
|
public function qualityDashboard(Request $request, QmsIntegrationService $qms)
|
|
{
|
|
$this->ensureModule('QualityManagement');
|
|
$businessId = $this->contextService->getBusinessId($request);
|
|
|
|
return $this->success([
|
|
'stats' => $qms->dashboardStats($businessId),
|
|
'recent_ncrs' => NcrReport::forBusiness($businessId)->latest('id')->limit(10)->get(['id', 'ref_no', 'title', 'severity', 'status']),
|
|
]);
|
|
}
|
|
|
|
public function qualityNcrIndex(Request $request)
|
|
{
|
|
$this->ensureModule('QualityManagement');
|
|
$businessId = $this->contextService->getBusinessId($request);
|
|
|
|
$ncrs = NcrReport::forBusiness($businessId)->latest('id')->paginate((int) $request->get('per_page', 20));
|
|
|
|
return $this->paginated($ncrs);
|
|
}
|
|
|
|
public function storeQualityNcr(Request $request, NcrWorkflowService $workflow, QmsIntegrationService $integration)
|
|
{
|
|
$this->ensureModule('QualityManagement');
|
|
$businessId = $this->contextService->getBusinessId($request);
|
|
|
|
$validated = $request->validate([
|
|
'title' => 'required|string|max:255',
|
|
'description' => 'nullable|string',
|
|
'severity' => 'required|in:minor,major,critical',
|
|
'source' => 'nullable|in:internal,customer,supplier,audit,inspection,production',
|
|
'contact_id' => 'nullable|integer',
|
|
'ie_bom_id' => 'nullable|integer',
|
|
'maintenance_equipment_id' => 'nullable|integer',
|
|
]);
|
|
|
|
$ncr = $workflow->createNcr($businessId, array_merge($validated, [
|
|
'source' => $validated['source'] ?? 'internal',
|
|
]), $integration);
|
|
|
|
return $this->success($ncr, 'NCR created.', 201);
|
|
}
|
|
|
|
public function storeProductionEvent(Request $request, ProductionEventService $eventService)
|
|
{
|
|
$this->ensureModule('ShopFloor');
|
|
$businessId = $this->contextService->getBusinessId($request);
|
|
|
|
$validated = $request->validate([
|
|
'work_center_id' => 'required|integer',
|
|
'ie_work_order_id' => 'nullable|integer',
|
|
'event_type' => 'required|in:start,stop,complete,scrap',
|
|
'quantity' => 'nullable|numeric|min:0',
|
|
'good_quantity' => 'nullable|numeric|min:0',
|
|
'scrap_quantity' => 'nullable|numeric|min:0',
|
|
'lot_no' => 'nullable|string|max:100',
|
|
'serial_no' => 'nullable|string|max:100',
|
|
'recorded_at' => 'nullable|date',
|
|
]);
|
|
|
|
WorkCenter::forBusiness($businessId)->findOrFail($validated['work_center_id']);
|
|
|
|
$event = $eventService->recordEvent($businessId, $validated);
|
|
|
|
return $this->success($event->load('workCenter'), 'Production event recorded.', 201);
|
|
}
|
|
|
|
public function submitRfqResponse($id, Request $request, RfqService $rfqService)
|
|
{
|
|
$this->ensureModule('SupplyChain');
|
|
$businessId = $this->contextService->getBusinessId($request);
|
|
|
|
$validated = $request->validate([
|
|
'contact_id' => 'required|integer',
|
|
'quoted_price' => 'required|numeric|min:0',
|
|
'lead_days' => 'required|integer|min:1',
|
|
'notes' => 'nullable|string|max:2000',
|
|
]);
|
|
|
|
$rfq = RfqRequest::forBusiness($businessId)->findOrFail($id);
|
|
|
|
$hasInvite = RfqResponse::where('rfq_id', $rfq->id)
|
|
->where('contact_id', $validated['contact_id'])
|
|
->exists();
|
|
|
|
if (! $hasInvite) {
|
|
return $this->error('Supplier not invited to this RFQ.', 404);
|
|
}
|
|
|
|
if ($rfq->status !== 'open') {
|
|
return $this->error('RFQ is not open for responses.', 422);
|
|
}
|
|
|
|
$response = $rfqService->submitResponse(
|
|
$rfq,
|
|
(int) $validated['contact_id'],
|
|
(float) $validated['quoted_price'],
|
|
(int) $validated['lead_days'],
|
|
$validated['notes'] ?? null
|
|
);
|
|
|
|
return $this->success($response, 'RFQ response submitted.');
|
|
}
|
|
|
|
public function shopFloorDashboard(Request $request)
|
|
{
|
|
$this->ensureModule('ShopFloor');
|
|
$businessId = $this->contextService->getBusinessId($request);
|
|
|
|
$workCenters = WorkCenter::forBusiness($businessId)->count();
|
|
$eventsToday = ProductionEvent::forBusiness($businessId)
|
|
->whereDate('recorded_at', today())
|
|
->count();
|
|
$avgOee = OeeSnapshot::forBusiness($businessId)
|
|
->where('snapshot_date', '>=', now()->subDays(30)->toDateString())
|
|
->avg('oee_pct');
|
|
|
|
return $this->success([
|
|
'work_centers' => $workCenters,
|
|
'production_events_today' => $eventsToday,
|
|
'avg_oee_30d' => round((float) $avgOee, 2),
|
|
'recent_events' => ProductionEvent::forBusiness($businessId)->with('workCenter')->latest('recorded_at')->limit(10)->get(),
|
|
]);
|
|
}
|
|
|
|
public function shopFloorOee(Request $request, OeeCalculationService $oee)
|
|
{
|
|
$this->ensureModule('ShopFloor');
|
|
$businessId = $this->contextService->getBusinessId($request);
|
|
$workCenterId = (int) $request->get('work_center_id');
|
|
|
|
if ($workCenterId) {
|
|
WorkCenter::forBusiness($businessId)->findOrFail($workCenterId);
|
|
$date = Carbon::parse($request->get('date', today()->toDateString()));
|
|
|
|
$snapshot = $oee->calculateForWorkCenter($businessId, $workCenterId, $date);
|
|
|
|
return $this->success($snapshot);
|
|
}
|
|
|
|
$snapshots = OeeSnapshot::forBusiness($businessId)
|
|
->where('snapshot_date', '>=', $request->get('from', now()->subDays(7)->toDateString()))
|
|
->orderByDesc('snapshot_date')
|
|
->limit(50)
|
|
->get();
|
|
|
|
return $this->success(['snapshots' => $snapshots]);
|
|
}
|
|
|
|
public function supplyChainDashboard(Request $request)
|
|
{
|
|
$this->ensureModule('SupplyChain');
|
|
$businessId = $this->contextService->getBusinessId($request);
|
|
|
|
return $this->success([
|
|
'scorecards' => Schema::hasTable('sc_supplier_scorecards')
|
|
? DB::table('sc_supplier_scorecards')->where('business_id', $businessId)->count()
|
|
: 0,
|
|
'forecasts' => Schema::hasTable('sc_demand_forecasts')
|
|
? DB::table('sc_demand_forecasts')->where('business_id', $businessId)->count()
|
|
: 0,
|
|
'open_rfqs' => Schema::hasTable('sc_rfq_requests')
|
|
? DB::table('sc_rfq_requests')->where('business_id', $businessId)->whereIn('status', ['draft', 'open'])->count()
|
|
: 0,
|
|
'avg_supplier_score' => Schema::hasTable('sc_supplier_scorecards')
|
|
? round((float) DB::table('sc_supplier_scorecards')->where('business_id', $businessId)->avg('overall_score'), 2)
|
|
: 0,
|
|
]);
|
|
}
|
|
|
|
public function supplyChainScorecards(Request $request)
|
|
{
|
|
$this->ensureModule('SupplyChain');
|
|
$businessId = $this->contextService->getBusinessId($request);
|
|
|
|
if (! Schema::hasTable('sc_supplier_scorecards')) {
|
|
return $this->success([]);
|
|
}
|
|
|
|
$rows = DB::table('sc_supplier_scorecards')
|
|
->where('business_id', $businessId)
|
|
->orderByDesc('overall_score')
|
|
->paginate((int) $request->get('per_page', 20));
|
|
|
|
return $this->paginated($rows);
|
|
}
|
|
|
|
public function advancedPlanningDashboard(Request $request)
|
|
{
|
|
$this->ensureModule('AdvancedPlanning');
|
|
$businessId = $this->contextService->getBusinessId($request);
|
|
|
|
return $this->success([
|
|
'capacity_resources' => Schema::hasTable('ap_capacity_resources')
|
|
? DB::table('ap_capacity_resources')->where('business_id', $businessId)->count()
|
|
: 0,
|
|
'schedule_runs' => Schema::hasTable('ap_schedule_runs')
|
|
? DB::table('ap_schedule_runs')->where('business_id', $businessId)->count()
|
|
: 0,
|
|
'sop_cycles' => Schema::hasTable('ap_sop_cycles')
|
|
? DB::table('ap_sop_cycles')->where('business_id', $businessId)->count()
|
|
: 0,
|
|
'latest_schedule' => Schema::hasTable('ap_schedule_runs')
|
|
? DB::table('ap_schedule_runs')->where('business_id', $businessId)->orderByDesc('id')->first()
|
|
: null,
|
|
]);
|
|
}
|
|
|
|
public function cpqIndex(Request $request)
|
|
{
|
|
$this->ensureModule('IndustrialEngineering');
|
|
$businessId = $this->contextService->getBusinessId($request);
|
|
|
|
if (! Schema::hasTable('ie_cpq_configurations')) {
|
|
return $this->success([]);
|
|
}
|
|
|
|
$configs = DB::table('ie_cpq_configurations')
|
|
->where('business_id', $businessId)
|
|
->whereNull('deleted_at')
|
|
->orderByDesc('id')
|
|
->paginate((int) $request->get('per_page', 20));
|
|
|
|
return $this->paginated($configs);
|
|
}
|
|
|
|
public function wmsDashboard(Request $request)
|
|
{
|
|
$this->ensureModule('InventoryManagement');
|
|
$businessId = $this->contextService->getBusinessId($request);
|
|
|
|
return $this->success([
|
|
'active_waves' => Schema::hasTable('im_wms_waves')
|
|
? DB::table('im_wms_waves')->where('business_id', $businessId)->whereIn('status', ['planned', 'released', 'picking'])->count()
|
|
: 0,
|
|
'yard_locations' => Schema::hasTable('im_wms_yard_locations')
|
|
? DB::table('im_wms_yard_locations')->where('business_id', $businessId)->count()
|
|
: 0,
|
|
'rf_scans_today' => Schema::hasTable('im_wms_rf_scans')
|
|
? DB::table('im_wms_rf_scans')->where('business_id', $businessId)->whereDate('scanned_at', today())->count()
|
|
: 0,
|
|
]);
|
|
}
|
|
|
|
public function integrationHubDashboard(Request $request)
|
|
{
|
|
$this->ensureModule('IntegrationHub');
|
|
$businessId = $this->contextService->getBusinessId($request);
|
|
|
|
return $this->success([
|
|
'webhooks' => Schema::hasTable('ih_webhook_endpoints')
|
|
? DB::table('ih_webhook_endpoints')->where('business_id', $businessId)->count()
|
|
: 0,
|
|
'odata_tokens' => Schema::hasTable('ih_odata_tokens')
|
|
? DB::table('ih_odata_tokens')->where('business_id', $businessId)->where('is_active', 1)->count()
|
|
: 0,
|
|
'export_jobs' => Schema::hasTable('ih_export_jobs')
|
|
? DB::table('ih_export_jobs')->where('business_id', $businessId)->count()
|
|
: 0,
|
|
'recent_exports' => Schema::hasTable('ih_export_jobs')
|
|
? DB::table('ih_export_jobs')->where('business_id', $businessId)->orderByDesc('id')->limit(5)->get()
|
|
: [],
|
|
]);
|
|
}
|
|
|
|
protected function ensureModule(string $module): void
|
|
{
|
|
if (! $this->moduleUtil->isModuleInstalled($module)) {
|
|
abort(403, 'Unauthorized action.');
|
|
}
|
|
}
|
|
}
|