Revert "feat: DB 연결 오버라이딩 및 대시보드 통계 위젯 추가"
This reverts commit bf8036a64b.
This commit is contained in:
@@ -1,335 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Design;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\BomConditionRuleService;
|
||||
use App\Http\Requests\Api\V1\Design\BomConditionRuleFormRequest;
|
||||
use App\Http\Requests\Api\V1\BomConditionRule\IndexBomConditionRuleRequest;
|
||||
|
||||
/**
|
||||
* @OA\Tag(name="BOM Condition Rules", description="BOM condition rule management APIs")
|
||||
*/
|
||||
class BomConditionRuleController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected BomConditionRuleService $service
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/v1/design/models/{modelId}/condition-rules",
|
||||
* summary="Get BOM condition rules",
|
||||
* description="Retrieve all condition rules for a specific model",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* tags={"BOM Condition Rules"},
|
||||
* @OA\Parameter(
|
||||
* name="modelId",
|
||||
* description="Model ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="page",
|
||||
* description="Page number for pagination",
|
||||
* in="query",
|
||||
* required=false,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="per_page",
|
||||
* description="Items per page",
|
||||
* in="query",
|
||||
* required=false,
|
||||
* @OA\Schema(type="integer", example=20)
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="search",
|
||||
* description="Search by rule name or condition",
|
||||
* in="query",
|
||||
* required=false,
|
||||
* @OA\Schema(type="string", example="bracket_selection")
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="priority",
|
||||
* description="Filter by priority level",
|
||||
* in="query",
|
||||
* required=false,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="BOM condition rules retrieved successfully",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(
|
||||
* property="data",
|
||||
* @OA\Property(
|
||||
* property="data",
|
||||
* type="array",
|
||||
* @OA\Items(ref="#/components/schemas/BomConditionRuleResource")
|
||||
* ),
|
||||
* @OA\Property(property="current_page", type="integer", example=1),
|
||||
* @OA\Property(property="last_page", type="integer", example=2),
|
||||
* @OA\Property(property="per_page", type="integer", example=20),
|
||||
* @OA\Property(property="total", type="integer", example=30)
|
||||
* )
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(ref="#/components/responses/ErrorResponse", response=400),
|
||||
* @OA\Response(ref="#/components/responses/UnauthorizedResponse", response=401),
|
||||
* @OA\Response(ref="#/components/responses/NotFoundResponse", response=404)
|
||||
* )
|
||||
*/
|
||||
public function index(IndexBomConditionRuleRequest $request, int $modelId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $modelId) {
|
||||
return $this->service->getModelConditionRules($modelId, $request->validated());
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/v1/design/models/{modelId}/condition-rules",
|
||||
* summary="Create BOM condition rule",
|
||||
* description="Create a new condition rule for a specific model",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* tags={"BOM Condition Rules"},
|
||||
* @OA\Parameter(
|
||||
* name="modelId",
|
||||
* description="Model ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(ref="#/components/schemas/CreateBomConditionRuleRequest")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=201,
|
||||
* description="BOM condition rule created successfully",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(property="data", ref="#/components/schemas/BomConditionRuleResource")
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(ref="#/components/responses/ValidationErrorResponse", response=422),
|
||||
* @OA\Response(ref="#/components/responses/ErrorResponse", response=400),
|
||||
* @OA\Response(ref="#/components/responses/UnauthorizedResponse", response=401)
|
||||
* )
|
||||
*/
|
||||
public function store(BomConditionRuleFormRequest $request, int $modelId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $modelId) {
|
||||
return $this->service->createConditionRule($modelId, $request->validated());
|
||||
}, __('message.created'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Put(
|
||||
* path="/v1/design/models/{modelId}/condition-rules/{ruleId}",
|
||||
* summary="Update BOM condition rule",
|
||||
* description="Update a specific BOM condition rule",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* tags={"BOM Condition Rules"},
|
||||
* @OA\Parameter(
|
||||
* name="modelId",
|
||||
* description="Model ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="ruleId",
|
||||
* description="Rule ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(ref="#/components/schemas/UpdateBomConditionRuleRequest")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="BOM condition rule updated successfully",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(property="data", ref="#/components/schemas/BomConditionRuleResource")
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(ref="#/components/responses/ValidationErrorResponse", response=422),
|
||||
* @OA\Response(ref="#/components/responses/ErrorResponse", response=400),
|
||||
* @OA\Response(ref="#/components/responses/UnauthorizedResponse", response=401),
|
||||
* @OA\Response(ref="#/components/responses/NotFoundResponse", response=404)
|
||||
* )
|
||||
*/
|
||||
public function update(BomConditionRuleFormRequest $request, int $modelId, int $ruleId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $modelId, $ruleId) {
|
||||
return $this->service->updateConditionRule($modelId, $ruleId, $request->validated());
|
||||
}, __('message.updated'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Delete(
|
||||
* path="/v1/design/models/{modelId}/condition-rules/{ruleId}",
|
||||
* summary="Delete BOM condition rule",
|
||||
* description="Delete a specific BOM condition rule",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* tags={"BOM Condition Rules"},
|
||||
* @OA\Parameter(
|
||||
* name="modelId",
|
||||
* description="Model ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="ruleId",
|
||||
* description="Rule ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="BOM condition rule deleted successfully",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ApiResponse")
|
||||
* ),
|
||||
* @OA\Response(ref="#/components/responses/ErrorResponse", response=400),
|
||||
* @OA\Response(ref="#/components/responses/UnauthorizedResponse", response=401),
|
||||
* @OA\Response(ref="#/components/responses/NotFoundResponse", response=404)
|
||||
* )
|
||||
*/
|
||||
public function destroy(int $modelId, int $ruleId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($modelId, $ruleId) {
|
||||
$this->service->deleteConditionRule($modelId, $ruleId);
|
||||
return null;
|
||||
}, __('message.deleted'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/v1/design/models/{modelId}/condition-rules/{ruleId}/validate",
|
||||
* summary="Validate BOM condition rule",
|
||||
* description="Validate condition rule expression and logic",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* tags={"BOM Condition Rules"},
|
||||
* @OA\Parameter(
|
||||
* name="modelId",
|
||||
* description="Model ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="ruleId",
|
||||
* description="Rule ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Condition rule validation result",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(
|
||||
* property="data",
|
||||
* @OA\Property(property="is_valid", type="boolean", example=true),
|
||||
* @OA\Property(
|
||||
* property="validation_errors",
|
||||
* type="array",
|
||||
* @OA\Items(type="string", example="Invalid condition syntax")
|
||||
* ),
|
||||
* @OA\Property(
|
||||
* property="tested_scenarios",
|
||||
* type="array",
|
||||
* @OA\Items(
|
||||
* @OA\Property(property="scenario", type="string", example="W0=1000, H0=800"),
|
||||
* @OA\Property(property="result", type="boolean", example=true)
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(ref="#/components/responses/ErrorResponse", response=400),
|
||||
* @OA\Response(ref="#/components/responses/UnauthorizedResponse", response=401),
|
||||
* @OA\Response(ref="#/components/responses/NotFoundResponse", response=404)
|
||||
* )
|
||||
*/
|
||||
public function validate(int $modelId, int $ruleId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($modelId, $ruleId) {
|
||||
return $this->service->validateConditionRule($modelId, $ruleId);
|
||||
}, __('message.condition_rule.validated'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/v1/design/models/{modelId}/condition-rules/reorder",
|
||||
* summary="Reorder BOM condition rules",
|
||||
* description="Change the priority order of condition rules",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* tags={"BOM Condition Rules"},
|
||||
* @OA\Parameter(
|
||||
* name="modelId",
|
||||
* description="Model ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(
|
||||
* property="rule_orders",
|
||||
* type="array",
|
||||
* @OA\Items(
|
||||
* @OA\Property(property="rule_id", type="integer", example=1),
|
||||
* @OA\Property(property="priority", type="integer", example=1)
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="BOM condition rules reordered successfully",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ApiResponse")
|
||||
* ),
|
||||
* @OA\Response(ref="#/components/responses/ValidationErrorResponse", response=422),
|
||||
* @OA\Response(ref="#/components/responses/ErrorResponse", response=400),
|
||||
* @OA\Response(ref="#/components/responses/UnauthorizedResponse", response=401),
|
||||
* @OA\Response(ref="#/components/responses/NotFoundResponse", response=404)
|
||||
* )
|
||||
*/
|
||||
public function reorder(int $modelId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($modelId) {
|
||||
$ruleOrders = request()->input('rule_orders', []);
|
||||
$this->service->reorderConditionRules($modelId, $ruleOrders);
|
||||
return null;
|
||||
}, __('message.reordered'));
|
||||
}
|
||||
}
|
||||
@@ -1,281 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Design;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\BomResolverService;
|
||||
use App\Http\Requests\Api\V1\Design\BomResolverFormRequest;
|
||||
|
||||
/**
|
||||
* @OA\Tag(name="BOM Resolver", description="BOM resolution and preview APIs")
|
||||
*/
|
||||
class BomResolverController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected BomResolverService $service
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/v1/design/models/{modelId}/resolve-bom",
|
||||
* summary="Resolve BOM preview",
|
||||
* description="Generate real-time BOM preview based on input parameters without creating actual products",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* tags={"BOM Resolver"},
|
||||
* @OA\Parameter(
|
||||
* name="modelId",
|
||||
* description="Model ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(ref="#/components/schemas/ResolvePreviewRequest")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="BOM preview generated successfully",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(property="data", ref="#/components/schemas/BomPreviewResponse")
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(ref="#/components/responses/ValidationErrorResponse", response=422),
|
||||
* @OA\Response(ref="#/components/responses/ErrorResponse", response=400),
|
||||
* @OA\Response(ref="#/components/responses/UnauthorizedResponse", response=401),
|
||||
* @OA\Response(ref="#/components/responses/NotFoundResponse", response=404)
|
||||
* )
|
||||
*/
|
||||
public function resolveBom(BomResolverFormRequest $request, int $modelId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $modelId) {
|
||||
return $this->service->generatePreview($modelId, $request->validated());
|
||||
}, __('message.bom.preview_generated'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/v1/design/models/{modelId}/validate-parameters",
|
||||
* summary="Validate model parameters",
|
||||
* description="Validate input parameters against model constraints before BOM resolution",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* tags={"BOM Resolver"},
|
||||
* @OA\Parameter(
|
||||
* name="modelId",
|
||||
* description="Model ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(
|
||||
* property="input_parameters",
|
||||
* description="Input parameter values to validate",
|
||||
* type="object",
|
||||
* additionalProperties=@OA\Property(oneOf={
|
||||
* @OA\Schema(type="number"),
|
||||
* @OA\Schema(type="string"),
|
||||
* @OA\Schema(type="boolean")
|
||||
* }),
|
||||
* example={"W0": 1000, "H0": 800, "installation_type": "A"}
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Parameter validation result",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(
|
||||
* property="data",
|
||||
* @OA\Property(property="is_valid", type="boolean", example=true),
|
||||
* @OA\Property(
|
||||
* property="validation_errors",
|
||||
* type="array",
|
||||
* @OA\Items(
|
||||
* @OA\Property(property="parameter", type="string", example="W0"),
|
||||
* @OA\Property(property="error", type="string", example="Value must be between 500 and 2000")
|
||||
* )
|
||||
* ),
|
||||
* @OA\Property(
|
||||
* property="warnings",
|
||||
* type="array",
|
||||
* @OA\Items(
|
||||
* @OA\Property(property="parameter", type="string", example="H0"),
|
||||
* @OA\Property(property="warning", type="string", example="Recommended range is 600-1500")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(ref="#/components/responses/ValidationErrorResponse", response=422),
|
||||
* @OA\Response(ref="#/components/responses/ErrorResponse", response=400),
|
||||
* @OA\Response(ref="#/components/responses/UnauthorizedResponse", response=401),
|
||||
* @OA\Response(ref="#/components/responses/NotFoundResponse", response=404)
|
||||
* )
|
||||
*/
|
||||
public function validateParameters(int $modelId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($modelId) {
|
||||
$inputParameters = request()->input('input_parameters', []);
|
||||
return $this->service->validateParameters($modelId, $inputParameters);
|
||||
}, __('message.parameters.validated'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/v1/design/models/{modelId}/parameter-schema",
|
||||
* summary="Get model parameter schema",
|
||||
* description="Retrieve the input parameter schema for a specific model",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* tags={"BOM Resolver"},
|
||||
* @OA\Parameter(
|
||||
* name="modelId",
|
||||
* description="Model ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Model parameter schema retrieved successfully",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(
|
||||
* property="data",
|
||||
* @OA\Property(
|
||||
* property="input_parameters",
|
||||
* type="array",
|
||||
* @OA\Items(
|
||||
* @OA\Property(property="name", type="string", example="W0"),
|
||||
* @OA\Property(property="label", type="string", example="Width"),
|
||||
* @OA\Property(property="data_type", type="string", example="INTEGER"),
|
||||
* @OA\Property(property="unit", type="string", example="mm"),
|
||||
* @OA\Property(property="min_value", type="number", example=500),
|
||||
* @OA\Property(property="max_value", type="number", example=3000),
|
||||
* @OA\Property(property="default_value", type="string", example="1000"),
|
||||
* @OA\Property(property="is_required", type="boolean", example=true),
|
||||
* @OA\Property(property="description", type="string", example="Product width in millimeters")
|
||||
* )
|
||||
* ),
|
||||
* @OA\Property(
|
||||
* property="output_parameters",
|
||||
* type="array",
|
||||
* @OA\Items(
|
||||
* @OA\Property(property="name", type="string", example="W1"),
|
||||
* @OA\Property(property="label", type="string", example="Actual Width"),
|
||||
* @OA\Property(property="data_type", type="string", example="INTEGER"),
|
||||
* @OA\Property(property="unit", type="string", example="mm"),
|
||||
* @OA\Property(property="description", type="string", example="Calculated actual width")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(ref="#/components/responses/ErrorResponse", response=400),
|
||||
* @OA\Response(ref="#/components/responses/UnauthorizedResponse", response=401),
|
||||
* @OA\Response(ref="#/components/responses/NotFoundResponse", response=404)
|
||||
* )
|
||||
*/
|
||||
public function getParameterSchema(int $modelId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($modelId) {
|
||||
return $this->service->getParameterSchema($modelId);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/v1/design/models/{modelId}/calculate-preview",
|
||||
* summary="Calculate output values preview",
|
||||
* description="Calculate output parameter values based on input parameters using formulas",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* tags={"BOM Resolver"},
|
||||
* @OA\Parameter(
|
||||
* name="modelId",
|
||||
* description="Model ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(
|
||||
* property="input_parameters",
|
||||
* description="Input parameter values",
|
||||
* type="object",
|
||||
* additionalProperties=@OA\Property(oneOf={
|
||||
* @OA\Schema(type="number"),
|
||||
* @OA\Schema(type="string"),
|
||||
* @OA\Schema(type="boolean")
|
||||
* }),
|
||||
* example={"W0": 1000, "H0": 800, "installation_type": "A"}
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Output values calculated successfully",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(
|
||||
* property="data",
|
||||
* @OA\Property(
|
||||
* property="calculated_values",
|
||||
* type="object",
|
||||
* additionalProperties=@OA\Property(oneOf={
|
||||
* @OA\Schema(type="number"),
|
||||
* @OA\Schema(type="string")
|
||||
* }),
|
||||
* example={"W1": 1050, "H1": 850, "area": 892500, "weight": 45.5}
|
||||
* ),
|
||||
* @OA\Property(
|
||||
* property="calculation_steps",
|
||||
* type="array",
|
||||
* @OA\Items(
|
||||
* @OA\Property(property="parameter", type="string", example="W1"),
|
||||
* @OA\Property(property="formula", type="string", example="W0 + 50"),
|
||||
* @OA\Property(property="result", oneOf={
|
||||
* @OA\Schema(type="number"),
|
||||
* @OA\Schema(type="string")
|
||||
* }, example=1050)
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(ref="#/components/responses/ValidationErrorResponse", response=422),
|
||||
* @OA\Response(ref="#/components/responses/ErrorResponse", response=400),
|
||||
* @OA\Response(ref="#/components/responses/UnauthorizedResponse", response=401),
|
||||
* @OA\Response(ref="#/components/responses/NotFoundResponse", response=404)
|
||||
* )
|
||||
*/
|
||||
public function calculatePreview(int $modelId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($modelId) {
|
||||
$inputParameters = request()->input('input_parameters', []);
|
||||
return $this->service->calculatePreview($modelId, $inputParameters);
|
||||
}, __('message.calculated'));
|
||||
}
|
||||
}
|
||||
@@ -1,278 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Design;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\ModelFormulaService;
|
||||
use App\Http\Requests\Api\V1\Design\ModelFormulaFormRequest;
|
||||
use App\Http\Requests\Api\V1\ModelFormula\IndexModelFormulaRequest;
|
||||
|
||||
/**
|
||||
* @OA\Tag(name="Model Formulas", description="Model formula management APIs")
|
||||
*/
|
||||
class ModelFormulaController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected ModelFormulaService $service
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/v1/design/models/{modelId}/formulas",
|
||||
* summary="Get model formulas",
|
||||
* description="Retrieve all formulas for a specific model",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* tags={"Model Formulas"},
|
||||
* @OA\Parameter(
|
||||
* name="modelId",
|
||||
* description="Model ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="page",
|
||||
* description="Page number for pagination",
|
||||
* in="query",
|
||||
* required=false,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="per_page",
|
||||
* description="Items per page",
|
||||
* in="query",
|
||||
* required=false,
|
||||
* @OA\Schema(type="integer", example=20)
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="search",
|
||||
* description="Search by formula name or target parameter",
|
||||
* in="query",
|
||||
* required=false,
|
||||
* @OA\Schema(type="string", example="calculate_area")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Model formulas retrieved successfully",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(
|
||||
* property="data",
|
||||
* @OA\Property(
|
||||
* property="data",
|
||||
* type="array",
|
||||
* @OA\Items(ref="#/components/schemas/ModelFormulaResource")
|
||||
* ),
|
||||
* @OA\Property(property="current_page", type="integer", example=1),
|
||||
* @OA\Property(property="last_page", type="integer", example=2),
|
||||
* @OA\Property(property="per_page", type="integer", example=20),
|
||||
* @OA\Property(property="total", type="integer", example=25)
|
||||
* )
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(ref="#/components/responses/ErrorResponse", response=400),
|
||||
* @OA\Response(ref="#/components/responses/UnauthorizedResponse", response=401),
|
||||
* @OA\Response(ref="#/components/responses/NotFoundResponse", response=404)
|
||||
* )
|
||||
*/
|
||||
public function index(IndexModelFormulaRequest $request, int $modelId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $modelId) {
|
||||
return $this->service->getModelFormulas($modelId, $request->validated());
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/v1/design/models/{modelId}/formulas",
|
||||
* summary="Create model formula",
|
||||
* description="Create a new formula for a specific model",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* tags={"Model Formulas"},
|
||||
* @OA\Parameter(
|
||||
* name="modelId",
|
||||
* description="Model ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(ref="#/components/schemas/CreateModelFormulaRequest")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=201,
|
||||
* description="Model formula created successfully",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(property="data", ref="#/components/schemas/ModelFormulaResource")
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(ref="#/components/responses/ValidationErrorResponse", response=422),
|
||||
* @OA\Response(ref="#/components/responses/ErrorResponse", response=400),
|
||||
* @OA\Response(ref="#/components/responses/UnauthorizedResponse", response=401)
|
||||
* )
|
||||
*/
|
||||
public function store(ModelFormulaFormRequest $request, int $modelId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $modelId) {
|
||||
return $this->service->createFormula($modelId, $request->validated());
|
||||
}, __('message.created'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Put(
|
||||
* path="/v1/design/models/{modelId}/formulas/{formulaId}",
|
||||
* summary="Update model formula",
|
||||
* description="Update a specific model formula",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* tags={"Model Formulas"},
|
||||
* @OA\Parameter(
|
||||
* name="modelId",
|
||||
* description="Model ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="formulaId",
|
||||
* description="Formula ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(ref="#/components/schemas/UpdateModelFormulaRequest")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Model formula updated successfully",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(property="data", ref="#/components/schemas/ModelFormulaResource")
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(ref="#/components/responses/ValidationErrorResponse", response=422),
|
||||
* @OA\Response(ref="#/components/responses/ErrorResponse", response=400),
|
||||
* @OA\Response(ref="#/components/responses/UnauthorizedResponse", response=401),
|
||||
* @OA\Response(ref="#/components/responses/NotFoundResponse", response=404)
|
||||
* )
|
||||
*/
|
||||
public function update(ModelFormulaFormRequest $request, int $modelId, int $formulaId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $modelId, $formulaId) {
|
||||
return $this->service->updateFormula($modelId, $formulaId, $request->validated());
|
||||
}, __('message.updated'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Delete(
|
||||
* path="/v1/design/models/{modelId}/formulas/{formulaId}",
|
||||
* summary="Delete model formula",
|
||||
* description="Delete a specific model formula",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* tags={"Model Formulas"},
|
||||
* @OA\Parameter(
|
||||
* name="modelId",
|
||||
* description="Model ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="formulaId",
|
||||
* description="Formula ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Model formula deleted successfully",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ApiResponse")
|
||||
* ),
|
||||
* @OA\Response(ref="#/components/responses/ErrorResponse", response=400),
|
||||
* @OA\Response(ref="#/components/responses/UnauthorizedResponse", response=401),
|
||||
* @OA\Response(ref="#/components/responses/NotFoundResponse", response=404)
|
||||
* )
|
||||
*/
|
||||
public function destroy(int $modelId, int $formulaId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($modelId, $formulaId) {
|
||||
$this->service->deleteFormula($modelId, $formulaId);
|
||||
return null;
|
||||
}, __('message.deleted'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/v1/design/models/{modelId}/formulas/{formulaId}/validate",
|
||||
* summary="Validate model formula",
|
||||
* description="Validate formula expression and dependencies",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* tags={"Model Formulas"},
|
||||
* @OA\Parameter(
|
||||
* name="modelId",
|
||||
* description="Model ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="formulaId",
|
||||
* description="Formula ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Formula validation result",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(
|
||||
* property="data",
|
||||
* @OA\Property(property="is_valid", type="boolean", example=true),
|
||||
* @OA\Property(
|
||||
* property="validation_errors",
|
||||
* type="array",
|
||||
* @OA\Items(type="string", example="Unknown variable: unknown_var")
|
||||
* ),
|
||||
* @OA\Property(
|
||||
* property="dependency_chain",
|
||||
* type="array",
|
||||
* @OA\Items(type="string", example="W0")
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(ref="#/components/responses/ErrorResponse", response=400),
|
||||
* @OA\Response(ref="#/components/responses/UnauthorizedResponse", response=401),
|
||||
* @OA\Response(ref="#/components/responses/NotFoundResponse", response=404)
|
||||
* )
|
||||
*/
|
||||
public function validate(int $modelId, int $formulaId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($modelId, $formulaId) {
|
||||
return $this->service->validateFormula($modelId, $formulaId);
|
||||
}, __('message.formula.validated'));
|
||||
}
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Design;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\ModelParameterService;
|
||||
use App\Http\Requests\Api\V1\Design\ModelParameterFormRequest;
|
||||
use App\Http\Requests\Api\V1\ModelParameter\IndexModelParameterRequest;
|
||||
|
||||
/**
|
||||
* @OA\Tag(name="Model Parameters", description="Model parameter management APIs")
|
||||
*/
|
||||
class ModelParameterController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected ModelParameterService $service
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/v1/design/models/{modelId}/parameters",
|
||||
* summary="Get model parameters",
|
||||
* description="Retrieve all parameters for a specific model",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* tags={"Model Parameters"},
|
||||
* @OA\Parameter(
|
||||
* name="modelId",
|
||||
* description="Model ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="page",
|
||||
* description="Page number for pagination",
|
||||
* in="query",
|
||||
* required=false,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="per_page",
|
||||
* description="Items per page",
|
||||
* in="query",
|
||||
* required=false,
|
||||
* @OA\Schema(type="integer", example=20)
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="search",
|
||||
* description="Search by parameter name or label",
|
||||
* in="query",
|
||||
* required=false,
|
||||
* @OA\Schema(type="string", example="width")
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="type",
|
||||
* description="Filter by parameter type",
|
||||
* in="query",
|
||||
* required=false,
|
||||
* @OA\Schema(type="string", enum={"INPUT", "OUTPUT"}, example="INPUT")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Model parameters retrieved successfully",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(
|
||||
* property="data",
|
||||
* @OA\Property(
|
||||
* property="data",
|
||||
* type="array",
|
||||
* @OA\Items(ref="#/components/schemas/ModelParameterResource")
|
||||
* ),
|
||||
* @OA\Property(property="current_page", type="integer", example=1),
|
||||
* @OA\Property(property="last_page", type="integer", example=3),
|
||||
* @OA\Property(property="per_page", type="integer", example=20),
|
||||
* @OA\Property(property="total", type="integer", example=45)
|
||||
* )
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(ref="#/components/responses/ErrorResponse", response=400),
|
||||
* @OA\Response(ref="#/components/responses/UnauthorizedResponse", response=401),
|
||||
* @OA\Response(ref="#/components/responses/NotFoundResponse", response=404)
|
||||
* )
|
||||
*/
|
||||
public function index(IndexModelParameterRequest $request, int $modelId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $modelId) {
|
||||
return $this->service->getModelParameters($modelId, $request->validated());
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/v1/design/models/{modelId}/parameters",
|
||||
* summary="Create model parameter",
|
||||
* description="Create a new parameter for a specific model",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* tags={"Model Parameters"},
|
||||
* @OA\Parameter(
|
||||
* name="modelId",
|
||||
* description="Model ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(ref="#/components/schemas/CreateModelParameterRequest")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=201,
|
||||
* description="Model parameter created successfully",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(property="data", ref="#/components/schemas/ModelParameterResource")
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(ref="#/components/responses/ValidationErrorResponse", response=422),
|
||||
* @OA\Response(ref="#/components/responses/ErrorResponse", response=400),
|
||||
* @OA\Response(ref="#/components/responses/UnauthorizedResponse", response=401)
|
||||
* )
|
||||
*/
|
||||
public function store(ModelParameterFormRequest $request, int $modelId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $modelId) {
|
||||
return $this->service->createParameter($modelId, $request->validated());
|
||||
}, __('message.created'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Put(
|
||||
* path="/v1/design/models/{modelId}/parameters/{parameterId}",
|
||||
* summary="Update model parameter",
|
||||
* description="Update a specific model parameter",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* tags={"Model Parameters"},
|
||||
* @OA\Parameter(
|
||||
* name="modelId",
|
||||
* description="Model ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="parameterId",
|
||||
* description="Parameter ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(ref="#/components/schemas/UpdateModelParameterRequest")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Model parameter updated successfully",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(property="data", ref="#/components/schemas/ModelParameterResource")
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(ref="#/components/responses/ValidationErrorResponse", response=422),
|
||||
* @OA\Response(ref="#/components/responses/ErrorResponse", response=400),
|
||||
* @OA\Response(ref="#/components/responses/UnauthorizedResponse", response=401),
|
||||
* @OA\Response(ref="#/components/responses/NotFoundResponse", response=404)
|
||||
* )
|
||||
*/
|
||||
public function update(ModelParameterFormRequest $request, int $modelId, int $parameterId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $modelId, $parameterId) {
|
||||
return $this->service->updateParameter($modelId, $parameterId, $request->validated());
|
||||
}, __('message.updated'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Delete(
|
||||
* path="/v1/design/models/{modelId}/parameters/{parameterId}",
|
||||
* summary="Delete model parameter",
|
||||
* description="Delete a specific model parameter",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* tags={"Model Parameters"},
|
||||
* @OA\Parameter(
|
||||
* name="modelId",
|
||||
* description="Model ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\Parameter(
|
||||
* name="parameterId",
|
||||
* description="Parameter ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Model parameter deleted successfully",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ApiResponse")
|
||||
* ),
|
||||
* @OA\Response(ref="#/components/responses/ErrorResponse", response=400),
|
||||
* @OA\Response(ref="#/components/responses/UnauthorizedResponse", response=401),
|
||||
* @OA\Response(ref="#/components/responses/NotFoundResponse", response=404)
|
||||
* )
|
||||
*/
|
||||
public function destroy(int $modelId, int $parameterId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($modelId, $parameterId) {
|
||||
$this->service->deleteParameter($modelId, $parameterId);
|
||||
return null;
|
||||
}, __('message.deleted'));
|
||||
}
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Design;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\ProductFromModelService;
|
||||
use App\Http\Requests\Api\V1\Design\ProductFromModelFormRequest;
|
||||
|
||||
/**
|
||||
* @OA\Tag(name="Product from Model", description="Product creation from model APIs")
|
||||
*/
|
||||
class ProductFromModelController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected ProductFromModelService $service
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/v1/design/models/{modelId}/create-product",
|
||||
* summary="Create product from model",
|
||||
* description="Create actual product with resolved BOM based on model and input parameters",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* tags={"Product from Model"},
|
||||
* @OA\Parameter(
|
||||
* name="modelId",
|
||||
* description="Model ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(ref="#/components/schemas/CreateProductFromModelRequest")
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=201,
|
||||
* description="Product created successfully with resolved BOM",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(property="data", ref="#/components/schemas/ProductWithBomResponse")
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(ref="#/components/responses/ValidationErrorResponse", response=422),
|
||||
* @OA\Response(ref="#/components/responses/ErrorResponse", response=400),
|
||||
* @OA\Response(ref="#/components/responses/UnauthorizedResponse", response=401),
|
||||
* @OA\Response(ref="#/components/responses/NotFoundResponse", response=404)
|
||||
* )
|
||||
*/
|
||||
public function createProduct(ProductFromModelFormRequest $request, int $modelId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $modelId) {
|
||||
$data = $request->validated();
|
||||
$data['model_id'] = $modelId;
|
||||
return $this->service->createProductWithBom($data);
|
||||
}, __('message.product.created_from_model'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/v1/products/{productId}/parameters",
|
||||
* summary="Get product parameters",
|
||||
* description="Retrieve parameters used to create this product",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* tags={"Product from Model"},
|
||||
* @OA\Parameter(
|
||||
* name="productId",
|
||||
* description="Product ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Product parameters retrieved successfully",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(property="data", ref="#/components/schemas/ProductParametersResponse")
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(ref="#/components/responses/ErrorResponse", response=400),
|
||||
* @OA\Response(ref="#/components/responses/UnauthorizedResponse", response=401),
|
||||
* @OA\Response(ref="#/components/responses/NotFoundResponse", response=404)
|
||||
* )
|
||||
*/
|
||||
public function getProductParameters(int $productId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($productId) {
|
||||
return $this->service->getProductParameters($productId);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/v1/products/{productId}/calculated-values",
|
||||
* summary="Get product calculated values",
|
||||
* description="Retrieve calculated output values for this product",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* tags={"Product from Model"},
|
||||
* @OA\Parameter(
|
||||
* name="productId",
|
||||
* description="Product ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Product calculated values retrieved successfully",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(property="data", ref="#/components/schemas/ProductCalculatedValuesResponse")
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(ref="#/components/responses/ErrorResponse", response=400),
|
||||
* @OA\Response(ref="#/components/responses/UnauthorizedResponse", response=401),
|
||||
* @OA\Response(ref="#/components/responses/NotFoundResponse", response=404)
|
||||
* )
|
||||
*/
|
||||
public function getCalculatedValues(int $productId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($productId) {
|
||||
return $this->service->getCalculatedValues($productId);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Post(
|
||||
* path="/v1/products/{productId}/recalculate",
|
||||
* summary="Recalculate product values",
|
||||
* description="Recalculate product BOM and values with updated parameters",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* tags={"Product from Model"},
|
||||
* @OA\Parameter(
|
||||
* name="productId",
|
||||
* description="Product ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\RequestBody(
|
||||
* required=true,
|
||||
* @OA\JsonContent(
|
||||
* @OA\Property(
|
||||
* property="input_parameters",
|
||||
* description="Updated input parameter values",
|
||||
* type="object",
|
||||
* additionalProperties=@OA\Property(oneOf={
|
||||
* @OA\Schema(type="number"),
|
||||
* @OA\Schema(type="string"),
|
||||
* @OA\Schema(type="boolean")
|
||||
* }),
|
||||
* example={"W0": 1200, "H0": 900, "installation_type": "B"}
|
||||
* ),
|
||||
* @OA\Property(
|
||||
* property="update_bom",
|
||||
* description="Whether to update the BOM components",
|
||||
* type="boolean",
|
||||
* example=true
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Product recalculated successfully",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(property="data", ref="#/components/schemas/ProductWithBomResponse")
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(ref="#/components/responses/ValidationErrorResponse", response=422),
|
||||
* @OA\Response(ref="#/components/responses/ErrorResponse", response=400),
|
||||
* @OA\Response(ref="#/components/responses/UnauthorizedResponse", response=401),
|
||||
* @OA\Response(ref="#/components/responses/NotFoundResponse", response=404)
|
||||
* )
|
||||
*/
|
||||
public function recalculate(int $productId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($productId) {
|
||||
$inputParameters = request()->input('input_parameters', []);
|
||||
$updateBom = request()->boolean('update_bom', true);
|
||||
|
||||
return $this->service->recalculateProduct($productId, $inputParameters, $updateBom);
|
||||
}, __('message.product.recalculated'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/v1/products/{productId}/model-info",
|
||||
* summary="Get product model information",
|
||||
* description="Retrieve the model information used to create this product",
|
||||
* security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}},
|
||||
* tags={"Product from Model"},
|
||||
* @OA\Parameter(
|
||||
* name="productId",
|
||||
* description="Product ID",
|
||||
* in="path",
|
||||
* required=true,
|
||||
* @OA\Schema(type="integer", example=1)
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Product model information retrieved successfully",
|
||||
* @OA\JsonContent(
|
||||
* allOf={
|
||||
* @OA\Schema(ref="#/components/schemas/ApiResponse"),
|
||||
* @OA\Schema(
|
||||
* @OA\Property(
|
||||
* property="data",
|
||||
* @OA\Property(property="model_id", type="integer", example=1),
|
||||
* @OA\Property(property="model_code", type="string", example="KSS01"),
|
||||
* @OA\Property(property="model_name", type="string", example="Standard Screen"),
|
||||
* @OA\Property(property="model_version", type="string", example="v1.0"),
|
||||
* @OA\Property(property="creation_timestamp", type="string", format="date-time"),
|
||||
* @OA\Property(
|
||||
* property="input_parameters_used",
|
||||
* type="object",
|
||||
* additionalProperties=@OA\Property(oneOf={
|
||||
* @OA\Schema(type="number"),
|
||||
* @OA\Schema(type="string"),
|
||||
* @OA\Schema(type="boolean")
|
||||
* }),
|
||||
* example={"W0": 1000, "H0": 800, "installation_type": "A"}
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* }
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(ref="#/components/responses/ErrorResponse", response=400),
|
||||
* @OA\Response(ref="#/components/responses/UnauthorizedResponse", response=401),
|
||||
* @OA\Response(ref="#/components/responses/NotFoundResponse", response=404)
|
||||
* )
|
||||
*/
|
||||
public function getModelInfo(int $productId)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($productId) {
|
||||
return $this->service->getProductModelInfo($productId);
|
||||
}, __('message.fetched'));
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Schemas;
|
||||
|
||||
use OpenApi\Attributes as OA;
|
||||
|
||||
/**
|
||||
* BOM Condition Rule related Swagger schemas
|
||||
*/
|
||||
|
||||
#[OA\Schema(
|
||||
schema: 'BomConditionRuleResource',
|
||||
description: 'BOM condition rule resource',
|
||||
properties: [
|
||||
new OA\Property(property: 'id', type: 'integer', example: 1),
|
||||
new OA\Property(property: 'bom_template_id', type: 'integer', example: 1),
|
||||
new OA\Property(property: 'name', type: 'string', example: '브라켓 선택 규칙'),
|
||||
new OA\Property(property: 'ref_type', type: 'string', enum: ['MATERIAL', 'PRODUCT'], example: 'MATERIAL'),
|
||||
new OA\Property(property: 'ref_id', type: 'integer', example: 101),
|
||||
new OA\Property(
|
||||
property: 'condition_expression',
|
||||
type: 'string',
|
||||
example: 'W1 >= 1000 && installation_type == "A"',
|
||||
description: 'Boolean expression to determine if this rule applies'
|
||||
),
|
||||
new OA\Property(
|
||||
property: 'quantity_expression',
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
example: 'ceiling(W1 / 500)',
|
||||
description: 'Expression to calculate required quantity'
|
||||
),
|
||||
new OA\Property(
|
||||
property: 'waste_rate_expression',
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
example: '0.05',
|
||||
description: 'Expression to calculate waste rate (0.0-1.0)'
|
||||
),
|
||||
new OA\Property(property: 'description', type: 'string', nullable: true, example: '가로 1000mm 이상, A타입 설치시 브라켓 적용'),
|
||||
new OA\Property(property: 'priority', type: 'integer', example: 1),
|
||||
new OA\Property(property: 'is_active', type: 'boolean', example: true),
|
||||
new OA\Property(property: 'created_at', type: 'string', format: 'date-time', example: '2024-01-01T00:00:00Z'),
|
||||
new OA\Property(property: 'updated_at', type: 'string', format: 'date-time', example: '2024-01-01T00:00:00Z'),
|
||||
new OA\Property(
|
||||
property: 'reference',
|
||||
type: 'object',
|
||||
description: 'Referenced material or product information',
|
||||
properties: [
|
||||
new OA\Property(property: 'id', type: 'integer', example: 101),
|
||||
new OA\Property(property: 'code', type: 'string', example: 'BR-001'),
|
||||
new OA\Property(property: 'name', type: 'string', example: '표준 브라켓'),
|
||||
new OA\Property(property: 'unit', type: 'string', example: 'EA')
|
||||
]
|
||||
)
|
||||
]
|
||||
)]
|
||||
class BomConditionRuleResource {}
|
||||
|
||||
#[OA\Schema(
|
||||
schema: 'CreateBomConditionRuleRequest',
|
||||
description: 'Request schema for creating BOM condition rule',
|
||||
required: ['name', 'ref_type', 'ref_id', 'condition_expression'],
|
||||
properties: [
|
||||
new OA\Property(property: 'name', type: 'string', maxLength: 100, example: '브라켓 선택 규칙'),
|
||||
new OA\Property(property: 'ref_type', type: 'string', enum: ['MATERIAL', 'PRODUCT'], example: 'MATERIAL'),
|
||||
new OA\Property(property: 'ref_id', type: 'integer', minimum: 1, example: 101),
|
||||
new OA\Property(
|
||||
property: 'condition_expression',
|
||||
type: 'string',
|
||||
maxLength: 1000,
|
||||
example: 'W1 >= 1000 && installation_type == "A"',
|
||||
description: 'Boolean expression to determine if this rule applies'
|
||||
),
|
||||
new OA\Property(
|
||||
property: 'quantity_expression',
|
||||
type: 'string',
|
||||
maxLength: 500,
|
||||
nullable: true,
|
||||
example: 'ceiling(W1 / 500)',
|
||||
description: 'Expression to calculate required quantity'
|
||||
),
|
||||
new OA\Property(
|
||||
property: 'waste_rate_expression',
|
||||
type: 'string',
|
||||
maxLength: 500,
|
||||
nullable: true,
|
||||
example: '0.05',
|
||||
description: 'Expression to calculate waste rate (0.0-1.0)'
|
||||
),
|
||||
new OA\Property(property: 'description', type: 'string', maxLength: 500, nullable: true, example: '가로 1000mm 이상, A타입 설치시 브라켓 적용'),
|
||||
new OA\Property(property: 'priority', type: 'integer', minimum: 0, example: 1),
|
||||
new OA\Property(property: 'is_active', type: 'boolean', example: true)
|
||||
]
|
||||
)]
|
||||
class CreateBomConditionRuleRequest {}
|
||||
|
||||
#[OA\Schema(
|
||||
schema: 'UpdateBomConditionRuleRequest',
|
||||
description: 'Request schema for updating BOM condition rule',
|
||||
properties: [
|
||||
new OA\Property(property: 'name', type: 'string', maxLength: 100, example: '브라켓 선택 규칙'),
|
||||
new OA\Property(property: 'ref_type', type: 'string', enum: ['MATERIAL', 'PRODUCT'], example: 'MATERIAL'),
|
||||
new OA\Property(property: 'ref_id', type: 'integer', minimum: 1, example: 101),
|
||||
new OA\Property(
|
||||
property: 'condition_expression',
|
||||
type: 'string',
|
||||
maxLength: 1000,
|
||||
example: 'W1 >= 1000 && installation_type == "A"',
|
||||
description: 'Boolean expression to determine if this rule applies'
|
||||
),
|
||||
new OA\Property(
|
||||
property: 'quantity_expression',
|
||||
type: 'string',
|
||||
maxLength: 500,
|
||||
nullable: true,
|
||||
example: 'ceiling(W1 / 500)',
|
||||
description: 'Expression to calculate required quantity'
|
||||
),
|
||||
new OA\Property(
|
||||
property: 'waste_rate_expression',
|
||||
type: 'string',
|
||||
maxLength: 500,
|
||||
nullable: true,
|
||||
example: '0.05',
|
||||
description: 'Expression to calculate waste rate (0.0-1.0)'
|
||||
),
|
||||
new OA\Property(property: 'description', type: 'string', maxLength: 500, nullable: true, example: '가로 1000mm 이상, A타입 설치시 브라켓 적용'),
|
||||
new OA\Property(property: 'priority', type: 'integer', minimum: 0, example: 1),
|
||||
new OA\Property(property: 'is_active', type: 'boolean', example: true)
|
||||
]
|
||||
)]
|
||||
class UpdateBomConditionRuleRequest {}
|
||||
@@ -1,314 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Schemas;
|
||||
|
||||
use OpenApi\Attributes as OA;
|
||||
|
||||
/**
|
||||
* BOM Resolver related Swagger schemas
|
||||
*/
|
||||
|
||||
#[OA\Schema(
|
||||
schema: 'ResolvePreviewRequest',
|
||||
description: 'Request schema for BOM preview resolution',
|
||||
required: ['input_parameters'],
|
||||
properties: [
|
||||
new OA\Property(
|
||||
property: 'input_parameters',
|
||||
type: 'object',
|
||||
additionalProperties: new OA\Property(oneOf: [
|
||||
new OA\Schema(type: 'number'),
|
||||
new OA\Schema(type: 'string'),
|
||||
new OA\Schema(type: 'boolean')
|
||||
]),
|
||||
example: [
|
||||
'W0' => 1000,
|
||||
'H0' => 800,
|
||||
'installation_type' => 'A',
|
||||
'power_source' => '220V'
|
||||
],
|
||||
description: 'Input parameter values for BOM resolution'
|
||||
),
|
||||
new OA\Property(property: 'bom_template_id', type: 'integer', minimum: 1, nullable: true, example: 1),
|
||||
new OA\Property(property: 'include_calculated_values', type: 'boolean', example: true),
|
||||
new OA\Property(property: 'include_bom_items', type: 'boolean', example: true)
|
||||
]
|
||||
)]
|
||||
class ResolvePreviewRequest {}
|
||||
|
||||
#[OA\Schema(
|
||||
schema: 'CreateProductFromModelRequest',
|
||||
description: 'Request schema for creating product from model',
|
||||
required: ['model_id', 'input_parameters', 'product_code', 'product_name'],
|
||||
properties: [
|
||||
new OA\Property(property: 'model_id', type: 'integer', minimum: 1, example: 1),
|
||||
new OA\Property(
|
||||
property: 'input_parameters',
|
||||
type: 'object',
|
||||
additionalProperties: new OA\Property(oneOf: [
|
||||
new OA\Schema(type: 'number'),
|
||||
new OA\Schema(type: 'string'),
|
||||
new OA\Schema(type: 'boolean')
|
||||
]),
|
||||
example: [
|
||||
'W0' => 1000,
|
||||
'H0' => 800,
|
||||
'installation_type' => 'A',
|
||||
'power_source' => '220V'
|
||||
],
|
||||
description: 'Input parameter values for BOM resolution'
|
||||
),
|
||||
new OA\Property(property: 'bom_template_id', type: 'integer', minimum: 1, nullable: true, example: 1),
|
||||
new OA\Property(
|
||||
property: 'product_code',
|
||||
type: 'string',
|
||||
maxLength: 50,
|
||||
example: 'KSS01-1000x800-A',
|
||||
description: 'Product code (uppercase letters, numbers, underscore, hyphen only)'
|
||||
),
|
||||
new OA\Property(property: 'product_name', type: 'string', maxLength: 100, example: 'KSS01 스크린 1000x800 A타입'),
|
||||
new OA\Property(property: 'category_id', type: 'integer', minimum: 1, nullable: true, example: 1),
|
||||
new OA\Property(property: 'description', type: 'string', maxLength: 1000, nullable: true, example: '매개변수 기반으로 생성된 맞춤형 스크린'),
|
||||
new OA\Property(property: 'unit', type: 'string', maxLength: 20, nullable: true, example: 'EA'),
|
||||
new OA\Property(property: 'min_order_qty', type: 'number', minimum: 0, nullable: true, example: 1),
|
||||
new OA\Property(property: 'lead_time_days', type: 'integer', minimum: 0, nullable: true, example: 7),
|
||||
new OA\Property(property: 'is_active', type: 'boolean', example: true),
|
||||
new OA\Property(property: 'create_bom_items', type: 'boolean', example: true),
|
||||
new OA\Property(property: 'validate_bom', type: 'boolean', example: true)
|
||||
]
|
||||
)]
|
||||
class CreateProductFromModelRequest {}
|
||||
|
||||
#[OA\Schema(
|
||||
schema: 'BomPreviewResponse',
|
||||
description: 'BOM preview resolution response',
|
||||
properties: [
|
||||
new OA\Property(
|
||||
property: 'input_parameters',
|
||||
type: 'object',
|
||||
additionalProperties: new OA\Property(oneOf: [
|
||||
new OA\Schema(type: 'number'),
|
||||
new OA\Schema(type: 'string'),
|
||||
new OA\Schema(type: 'boolean')
|
||||
]),
|
||||
example: [
|
||||
'W0' => 1000,
|
||||
'H0' => 800,
|
||||
'installation_type' => 'A',
|
||||
'power_source' => '220V'
|
||||
]
|
||||
),
|
||||
new OA\Property(
|
||||
property: 'calculated_values',
|
||||
type: 'object',
|
||||
additionalProperties: new OA\Property(type: 'number'),
|
||||
example: [
|
||||
'W1' => 1050,
|
||||
'H1' => 850,
|
||||
'area' => 892500,
|
||||
'weight' => 25.5,
|
||||
'motor_power' => 120
|
||||
]
|
||||
),
|
||||
new OA\Property(
|
||||
property: 'bom_items',
|
||||
type: 'array',
|
||||
items: new OA\Items(ref: '#/components/schemas/BomItemPreview')
|
||||
),
|
||||
new OA\Property(
|
||||
property: 'summary',
|
||||
type: 'object',
|
||||
properties: [
|
||||
new OA\Property(property: 'total_materials', type: 'integer', example: 15),
|
||||
new OA\Property(property: 'total_cost', type: 'number', example: 125000.50),
|
||||
new OA\Property(property: 'estimated_weight', type: 'number', example: 25.5)
|
||||
]
|
||||
),
|
||||
new OA\Property(
|
||||
property: 'validation_warnings',
|
||||
type: 'array',
|
||||
items: new OA\Items(
|
||||
properties: [
|
||||
new OA\Property(property: 'type', type: 'string', example: 'PARAMETER_OUT_OF_RANGE'),
|
||||
new OA\Property(property: 'message', type: 'string', example: 'W0 값이 권장 범위를 벗어났습니다'),
|
||||
new OA\Property(property: 'parameter', type: 'string', example: 'W0'),
|
||||
new OA\Property(property: 'current_value', type: 'number', example: 1000),
|
||||
new OA\Property(property: 'recommended_range', type: 'string', example: '600-900')
|
||||
],
|
||||
type: 'object'
|
||||
)
|
||||
)
|
||||
]
|
||||
)]
|
||||
class BomPreviewResponse {}
|
||||
|
||||
#[OA\Schema(
|
||||
schema: 'BomItemPreview',
|
||||
description: 'BOM item preview',
|
||||
properties: [
|
||||
new OA\Property(property: 'ref_type', type: 'string', enum: ['MATERIAL', 'PRODUCT'], example: 'MATERIAL'),
|
||||
new OA\Property(property: 'ref_id', type: 'integer', example: 101),
|
||||
new OA\Property(property: 'ref_code', type: 'string', example: 'BR-001'),
|
||||
new OA\Property(property: 'ref_name', type: 'string', example: '표준 브라켓'),
|
||||
new OA\Property(property: 'quantity', type: 'number', example: 2.0),
|
||||
new OA\Property(property: 'waste_rate', type: 'number', example: 0.05),
|
||||
new OA\Property(property: 'total_quantity', type: 'number', example: 2.1),
|
||||
new OA\Property(property: 'unit', type: 'string', example: 'EA'),
|
||||
new OA\Property(property: 'unit_cost', type: 'number', nullable: true, example: 5000.0),
|
||||
new OA\Property(property: 'total_cost', type: 'number', nullable: true, example: 10500.0),
|
||||
new OA\Property(property: 'applied_rule', type: 'string', nullable: true, example: '브라켓 선택 규칙'),
|
||||
new OA\Property(
|
||||
property: 'calculation_details',
|
||||
type: 'object',
|
||||
properties: [
|
||||
new OA\Property(property: 'condition_matched', type: 'boolean', example: true),
|
||||
new OA\Property(property: 'quantity_expression', type: 'string', example: 'ceiling(W1 / 500)'),
|
||||
new OA\Property(property: 'quantity_calculation', type: 'string', example: 'ceiling(1050 / 500) = 3')
|
||||
]
|
||||
)
|
||||
]
|
||||
)]
|
||||
class BomItemPreview {}
|
||||
|
||||
#[OA\Schema(
|
||||
schema: 'ProductWithBomResponse',
|
||||
description: 'Product created with BOM response',
|
||||
properties: [
|
||||
new OA\Property(
|
||||
property: 'product',
|
||||
type: 'object',
|
||||
properties: [
|
||||
new OA\Property(property: 'id', type: 'integer', example: 123),
|
||||
new OA\Property(property: 'code', type: 'string', example: 'KSS01-1000x800-A'),
|
||||
new OA\Property(property: 'name', type: 'string', example: 'KSS01 스크린 1000x800 A타입'),
|
||||
new OA\Property(property: 'type', type: 'string', example: 'PRODUCT'),
|
||||
new OA\Property(property: 'category_id', type: 'integer', nullable: true, example: 1),
|
||||
new OA\Property(property: 'description', type: 'string', nullable: true, example: '매개변수 기반으로 생성된 맞춤형 스크린'),
|
||||
new OA\Property(property: 'unit', type: 'string', nullable: true, example: 'EA'),
|
||||
new OA\Property(property: 'min_order_qty', type: 'number', nullable: true, example: 1),
|
||||
new OA\Property(property: 'lead_time_days', type: 'integer', nullable: true, example: 7),
|
||||
new OA\Property(property: 'is_active', type: 'boolean', example: true),
|
||||
new OA\Property(property: 'created_at', type: 'string', format: 'date-time', example: '2024-01-01T00:00:00Z')
|
||||
]
|
||||
),
|
||||
new OA\Property(
|
||||
property: 'input_parameters',
|
||||
type: 'object',
|
||||
additionalProperties: new OA\Property(oneOf: [
|
||||
new OA\Schema(type: 'number'),
|
||||
new OA\Schema(type: 'string'),
|
||||
new OA\Schema(type: 'boolean')
|
||||
]),
|
||||
example: [
|
||||
'W0' => 1000,
|
||||
'H0' => 800,
|
||||
'installation_type' => 'A',
|
||||
'power_source' => '220V'
|
||||
]
|
||||
),
|
||||
new OA\Property(
|
||||
property: 'calculated_values',
|
||||
type: 'object',
|
||||
additionalProperties: new OA\Property(type: 'number'),
|
||||
example: [
|
||||
'W1' => 1050,
|
||||
'H1' => 850,
|
||||
'area' => 892500,
|
||||
'weight' => 25.5
|
||||
]
|
||||
),
|
||||
new OA\Property(
|
||||
property: 'bom_items',
|
||||
type: 'array',
|
||||
items: new OA\Items(
|
||||
properties: [
|
||||
new OA\Property(property: 'id', type: 'integer', example: 1),
|
||||
new OA\Property(property: 'ref_type', type: 'string', enum: ['MATERIAL', 'PRODUCT'], example: 'MATERIAL'),
|
||||
new OA\Property(property: 'ref_id', type: 'integer', example: 101),
|
||||
new OA\Property(property: 'ref_code', type: 'string', example: 'BR-001'),
|
||||
new OA\Property(property: 'ref_name', type: 'string', example: '표준 브라켓'),
|
||||
new OA\Property(property: 'quantity', type: 'number', example: 2.0),
|
||||
new OA\Property(property: 'waste_rate', type: 'number', example: 0.05),
|
||||
new OA\Property(property: 'unit', type: 'string', example: 'EA')
|
||||
],
|
||||
type: 'object'
|
||||
)
|
||||
),
|
||||
new OA\Property(
|
||||
property: 'summary',
|
||||
type: 'object',
|
||||
properties: [
|
||||
new OA\Property(property: 'total_bom_items', type: 'integer', example: 15),
|
||||
new OA\Property(property: 'model_id', type: 'integer', example: 1),
|
||||
new OA\Property(property: 'bom_template_id', type: 'integer', nullable: true, example: 1)
|
||||
]
|
||||
)
|
||||
]
|
||||
)]
|
||||
class ProductWithBomResponse {}
|
||||
|
||||
#[OA\Schema(
|
||||
schema: 'ProductParametersResponse',
|
||||
description: 'Product parameters response',
|
||||
properties: [
|
||||
new OA\Property(property: 'product_id', type: 'integer', example: 123),
|
||||
new OA\Property(property: 'model_id', type: 'integer', example: 1),
|
||||
new OA\Property(
|
||||
property: 'input_parameters',
|
||||
type: 'object',
|
||||
additionalProperties: new OA\Property(oneOf: [
|
||||
new OA\Schema(type: 'number'),
|
||||
new OA\Schema(type: 'string'),
|
||||
new OA\Schema(type: 'boolean')
|
||||
]),
|
||||
example: [
|
||||
'W0' => 1000,
|
||||
'H0' => 800,
|
||||
'installation_type' => 'A',
|
||||
'power_source' => '220V'
|
||||
]
|
||||
),
|
||||
new OA\Property(
|
||||
property: 'parameter_definitions',
|
||||
type: 'array',
|
||||
items: new OA\Items(ref: '#/components/schemas/ModelParameterResource')
|
||||
)
|
||||
]
|
||||
)]
|
||||
class ProductParametersResponse {}
|
||||
|
||||
#[OA\Schema(
|
||||
schema: 'ProductCalculatedValuesResponse',
|
||||
description: 'Product calculated values response',
|
||||
properties: [
|
||||
new OA\Property(property: 'product_id', type: 'integer', example: 123),
|
||||
new OA\Property(property: 'model_id', type: 'integer', example: 1),
|
||||
new OA\Property(
|
||||
property: 'calculated_values',
|
||||
type: 'object',
|
||||
additionalProperties: new OA\Property(type: 'number'),
|
||||
example: [
|
||||
'W1' => 1050,
|
||||
'H1' => 850,
|
||||
'area' => 892500,
|
||||
'weight' => 25.5,
|
||||
'motor_power' => 120
|
||||
]
|
||||
),
|
||||
new OA\Property(
|
||||
property: 'formula_applications',
|
||||
type: 'array',
|
||||
items: new OA\Items(
|
||||
properties: [
|
||||
new OA\Property(property: 'formula_name', type: 'string', example: '최종 가로 크기 계산'),
|
||||
new OA\Property(property: 'target_parameter', type: 'string', example: 'W1'),
|
||||
new OA\Property(property: 'expression', type: 'string', example: 'W0 + (installation_type == "A" ? 50 : 30)'),
|
||||
new OA\Property(property: 'calculated_value', type: 'number', example: 1050),
|
||||
new OA\Property(property: 'execution_order', type: 'integer', example: 1)
|
||||
],
|
||||
type: 'object'
|
||||
)
|
||||
)
|
||||
]
|
||||
)]
|
||||
class ProductCalculatedValuesResponse {}
|
||||
@@ -1,80 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Schemas;
|
||||
|
||||
use OpenApi\Attributes as OA;
|
||||
|
||||
/**
|
||||
* Model Formula related Swagger schemas
|
||||
*/
|
||||
|
||||
#[OA\Schema(
|
||||
schema: 'ModelFormulaResource',
|
||||
description: 'Model formula resource',
|
||||
properties: [
|
||||
new OA\Property(property: 'id', type: 'integer', example: 1),
|
||||
new OA\Property(property: 'model_id', type: 'integer', example: 1),
|
||||
new OA\Property(property: 'name', type: 'string', example: '최종 가로 크기 계산'),
|
||||
new OA\Property(property: 'target_parameter', type: 'string', example: 'W1'),
|
||||
new OA\Property(property: 'expression', type: 'string', example: 'W0 + (installation_type == "A" ? 50 : 30)'),
|
||||
new OA\Property(property: 'description', type: 'string', nullable: true, example: '설치 타입에 따른 최종 가로 크기 계산'),
|
||||
new OA\Property(property: 'is_active', type: 'boolean', example: true),
|
||||
new OA\Property(property: 'execution_order', type: 'integer', example: 1),
|
||||
new OA\Property(property: 'created_at', type: 'string', format: 'date-time', example: '2024-01-01T00:00:00Z'),
|
||||
new OA\Property(property: 'updated_at', type: 'string', format: 'date-time', example: '2024-01-01T00:00:00Z')
|
||||
]
|
||||
)]
|
||||
class ModelFormulaResource {}
|
||||
|
||||
#[OA\Schema(
|
||||
schema: 'CreateModelFormulaRequest',
|
||||
description: 'Request schema for creating model formula',
|
||||
required: ['name', 'target_parameter', 'expression'],
|
||||
properties: [
|
||||
new OA\Property(property: 'name', type: 'string', maxLength: 100, example: '최종 가로 크기 계산'),
|
||||
new OA\Property(
|
||||
property: 'target_parameter',
|
||||
type: 'string',
|
||||
maxLength: 50,
|
||||
example: 'W1',
|
||||
description: 'Target parameter name (alphanumeric with underscore, must start with letter)'
|
||||
),
|
||||
new OA\Property(
|
||||
property: 'expression',
|
||||
type: 'string',
|
||||
maxLength: 1000,
|
||||
example: 'W0 + (installation_type == "A" ? 50 : 30)',
|
||||
description: 'Mathematical expression for calculating the target parameter'
|
||||
),
|
||||
new OA\Property(property: 'description', type: 'string', maxLength: 500, nullable: true, example: '설치 타입에 따른 최종 가로 크기 계산'),
|
||||
new OA\Property(property: 'is_active', type: 'boolean', example: true),
|
||||
new OA\Property(property: 'execution_order', type: 'integer', minimum: 0, example: 1)
|
||||
]
|
||||
)]
|
||||
class CreateModelFormulaRequest {}
|
||||
|
||||
#[OA\Schema(
|
||||
schema: 'UpdateModelFormulaRequest',
|
||||
description: 'Request schema for updating model formula',
|
||||
properties: [
|
||||
new OA\Property(property: 'name', type: 'string', maxLength: 100, example: '최종 가로 크기 계산'),
|
||||
new OA\Property(
|
||||
property: 'target_parameter',
|
||||
type: 'string',
|
||||
maxLength: 50,
|
||||
example: 'W1',
|
||||
description: 'Target parameter name (alphanumeric with underscore, must start with letter)'
|
||||
),
|
||||
new OA\Property(
|
||||
property: 'expression',
|
||||
type: 'string',
|
||||
maxLength: 1000,
|
||||
example: 'W0 + (installation_type == "A" ? 50 : 30)',
|
||||
description: 'Mathematical expression for calculating the target parameter'
|
||||
),
|
||||
new OA\Property(property: 'description', type: 'string', maxLength: 500, nullable: true, example: '설치 타입에 따른 최종 가로 크기 계산'),
|
||||
new OA\Property(property: 'is_active', type: 'boolean', example: true),
|
||||
new OA\Property(property: 'execution_order', type: 'integer', minimum: 0, example: 1)
|
||||
]
|
||||
)]
|
||||
class UpdateModelFormulaRequest {}
|
||||
@@ -1,107 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1\Schemas;
|
||||
|
||||
use OpenApi\Attributes as OA;
|
||||
|
||||
/**
|
||||
* Model Parameter related Swagger schemas
|
||||
*/
|
||||
|
||||
#[OA\Schema(
|
||||
schema: 'ModelParameterResource',
|
||||
description: 'Model parameter resource',
|
||||
properties: [
|
||||
new OA\Property(property: 'id', type: 'integer', example: 1),
|
||||
new OA\Property(property: 'model_id', type: 'integer', example: 1),
|
||||
new OA\Property(property: 'name', type: 'string', example: 'W0'),
|
||||
new OA\Property(property: 'label', type: 'string', example: '가로 크기'),
|
||||
new OA\Property(property: 'type', type: 'string', enum: ['INPUT', 'OUTPUT'], example: 'INPUT'),
|
||||
new OA\Property(property: 'data_type', type: 'string', enum: ['INTEGER', 'DECIMAL', 'STRING', 'BOOLEAN'], example: 'DECIMAL'),
|
||||
new OA\Property(property: 'unit', type: 'string', nullable: true, example: 'mm'),
|
||||
new OA\Property(property: 'default_value', type: 'string', nullable: true, example: '1000'),
|
||||
new OA\Property(property: 'min_value', type: 'number', nullable: true, example: 500),
|
||||
new OA\Property(property: 'max_value', type: 'number', nullable: true, example: 2000),
|
||||
new OA\Property(
|
||||
property: 'enum_values',
|
||||
type: 'array',
|
||||
items: new OA\Items(type: 'string'),
|
||||
nullable: true,
|
||||
example: ['A', 'B', 'C']
|
||||
),
|
||||
new OA\Property(property: 'validation_rules', type: 'string', nullable: true, example: 'required|numeric|min:500'),
|
||||
new OA\Property(property: 'description', type: 'string', nullable: true, example: '제품의 가로 크기를 입력하세요'),
|
||||
new OA\Property(property: 'is_required', type: 'boolean', example: true),
|
||||
new OA\Property(property: 'display_order', type: 'integer', example: 1),
|
||||
new OA\Property(property: 'created_at', type: 'string', format: 'date-time', example: '2024-01-01T00:00:00Z'),
|
||||
new OA\Property(property: 'updated_at', type: 'string', format: 'date-time', example: '2024-01-01T00:00:00Z')
|
||||
]
|
||||
)]
|
||||
class ModelParameterResource {}
|
||||
|
||||
#[OA\Schema(
|
||||
schema: 'CreateModelParameterRequest',
|
||||
description: 'Request schema for creating model parameter',
|
||||
required: ['name', 'label', 'type', 'data_type'],
|
||||
properties: [
|
||||
new OA\Property(
|
||||
property: 'name',
|
||||
type: 'string',
|
||||
maxLength: 50,
|
||||
example: 'W0',
|
||||
description: 'Parameter name (alphanumeric with underscore, must start with letter)'
|
||||
),
|
||||
new OA\Property(property: 'label', type: 'string', maxLength: 100, example: '가로 크기'),
|
||||
new OA\Property(property: 'type', type: 'string', enum: ['INPUT', 'OUTPUT'], example: 'INPUT'),
|
||||
new OA\Property(property: 'data_type', type: 'string', enum: ['INTEGER', 'DECIMAL', 'STRING', 'BOOLEAN'], example: 'DECIMAL'),
|
||||
new OA\Property(property: 'unit', type: 'string', maxLength: 20, nullable: true, example: 'mm'),
|
||||
new OA\Property(property: 'default_value', type: 'string', maxLength: 255, nullable: true, example: '1000'),
|
||||
new OA\Property(property: 'min_value', type: 'number', nullable: true, example: 500),
|
||||
new OA\Property(property: 'max_value', type: 'number', nullable: true, example: 2000),
|
||||
new OA\Property(
|
||||
property: 'enum_values',
|
||||
type: 'array',
|
||||
items: new OA\Items(type: 'string'),
|
||||
nullable: true,
|
||||
example: ['A', 'B', 'C']
|
||||
),
|
||||
new OA\Property(property: 'validation_rules', type: 'string', maxLength: 500, nullable: true, example: 'required|numeric|min:500'),
|
||||
new OA\Property(property: 'description', type: 'string', maxLength: 500, nullable: true, example: '제품의 가로 크기를 입력하세요'),
|
||||
new OA\Property(property: 'is_required', type: 'boolean', example: true),
|
||||
new OA\Property(property: 'display_order', type: 'integer', minimum: 0, example: 1)
|
||||
]
|
||||
)]
|
||||
class CreateModelParameterRequest {}
|
||||
|
||||
#[OA\Schema(
|
||||
schema: 'UpdateModelParameterRequest',
|
||||
description: 'Request schema for updating model parameter',
|
||||
properties: [
|
||||
new OA\Property(
|
||||
property: 'name',
|
||||
type: 'string',
|
||||
maxLength: 50,
|
||||
example: 'W0',
|
||||
description: 'Parameter name (alphanumeric with underscore, must start with letter)'
|
||||
),
|
||||
new OA\Property(property: 'label', type: 'string', maxLength: 100, example: '가로 크기'),
|
||||
new OA\Property(property: 'type', type: 'string', enum: ['INPUT', 'OUTPUT'], example: 'INPUT'),
|
||||
new OA\Property(property: 'data_type', type: 'string', enum: ['INTEGER', 'DECIMAL', 'STRING', 'BOOLEAN'], example: 'DECIMAL'),
|
||||
new OA\Property(property: 'unit', type: 'string', maxLength: 20, nullable: true, example: 'mm'),
|
||||
new OA\Property(property: 'default_value', type: 'string', maxLength: 255, nullable: true, example: '1000'),
|
||||
new OA\Property(property: 'min_value', type: 'number', nullable: true, example: 500),
|
||||
new OA\Property(property: 'max_value', type: 'number', nullable: true, example: 2000),
|
||||
new OA\Property(
|
||||
property: 'enum_values',
|
||||
type: 'array',
|
||||
items: new OA\Items(type: 'string'),
|
||||
nullable: true,
|
||||
example: ['A', 'B', 'C']
|
||||
),
|
||||
new OA\Property(property: 'validation_rules', type: 'string', maxLength: 500, nullable: true, example: 'required|numeric|min:500'),
|
||||
new OA\Property(property: 'description', type: 'string', maxLength: 500, nullable: true, example: '제품의 가로 크기를 입력하세요'),
|
||||
new OA\Property(property: 'is_required', type: 'boolean', example: true),
|
||||
new OA\Property(property: 'display_order', type: 'integer', minimum: 0, example: 1)
|
||||
]
|
||||
)]
|
||||
class UpdateModelParameterRequest {}
|
||||
@@ -1,63 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\BomConditionRule;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class CreateBomConditionRuleRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:100'],
|
||||
'ref_type' => ['required', 'string', 'in:MATERIAL,PRODUCT'],
|
||||
'ref_id' => ['required', 'integer', 'min:1'],
|
||||
'condition_expression' => ['required', 'string', 'max:1000'],
|
||||
'quantity_expression' => ['nullable', 'string', 'max:500'],
|
||||
'waste_rate_expression' => ['nullable', 'string', 'max:500'],
|
||||
'description' => ['nullable', 'string', 'max:500'],
|
||||
'priority' => ['integer', 'min:0'],
|
||||
'is_active' => ['boolean'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => __('validation.attributes.rule_name'),
|
||||
'ref_type' => __('validation.attributes.ref_type'),
|
||||
'ref_id' => __('validation.attributes.ref_id'),
|
||||
'condition_expression' => __('validation.attributes.condition_expression'),
|
||||
'quantity_expression' => __('validation.attributes.quantity_expression'),
|
||||
'waste_rate_expression' => __('validation.attributes.waste_rate_expression'),
|
||||
'description' => __('validation.attributes.description'),
|
||||
'priority' => __('validation.attributes.priority'),
|
||||
'is_active' => __('validation.attributes.is_active'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'is_active' => $this->boolean('is_active', true),
|
||||
'priority' => $this->integer('priority', 0),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\BomConditionRule;
|
||||
|
||||
use App\Http\Requests\Api\V1\PaginateRequest;
|
||||
|
||||
class IndexBomConditionRuleRequest extends PaginateRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return array_merge(parent::rules(), [
|
||||
'search' => ['sometimes', 'string', 'max:255'],
|
||||
'ref_type' => ['sometimes', 'string', 'in:MATERIAL,PRODUCT'],
|
||||
'is_active' => ['sometimes', 'boolean'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return array_merge(parent::attributes(), [
|
||||
'search' => __('validation.attributes.search'),
|
||||
'ref_type' => __('validation.attributes.ref_type'),
|
||||
'is_active' => __('validation.attributes.is_active'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
parent::prepareForValidation();
|
||||
|
||||
if ($this->has('is_active')) {
|
||||
$this->merge(['is_active' => $this->boolean('is_active')]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\BomConditionRule;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateBomConditionRuleRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['sometimes', 'string', 'max:100'],
|
||||
'ref_type' => ['sometimes', 'string', 'in:MATERIAL,PRODUCT'],
|
||||
'ref_id' => ['sometimes', 'integer', 'min:1'],
|
||||
'condition_expression' => ['sometimes', 'string', 'max:1000'],
|
||||
'quantity_expression' => ['nullable', 'string', 'max:500'],
|
||||
'waste_rate_expression' => ['nullable', 'string', 'max:500'],
|
||||
'description' => ['nullable', 'string', 'max:500'],
|
||||
'priority' => ['sometimes', 'integer', 'min:0'],
|
||||
'is_active' => ['sometimes', 'boolean'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => __('validation.attributes.rule_name'),
|
||||
'ref_type' => __('validation.attributes.ref_type'),
|
||||
'ref_id' => __('validation.attributes.ref_id'),
|
||||
'condition_expression' => __('validation.attributes.condition_expression'),
|
||||
'quantity_expression' => __('validation.attributes.quantity_expression'),
|
||||
'waste_rate_expression' => __('validation.attributes.waste_rate_expression'),
|
||||
'description' => __('validation.attributes.description'),
|
||||
'priority' => __('validation.attributes.priority'),
|
||||
'is_active' => __('validation.attributes.is_active'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
if ($this->has('is_active')) {
|
||||
$this->merge(['is_active' => $this->boolean('is_active')]);
|
||||
}
|
||||
|
||||
if ($this->has('priority')) {
|
||||
$this->merge(['priority' => $this->integer('priority')]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\BomResolver;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class CreateProductFromModelRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'model_id' => ['required', 'integer', 'min:1'],
|
||||
'input_parameters' => ['required', 'array', 'min:1'],
|
||||
'input_parameters.*' => ['required'],
|
||||
'bom_template_id' => ['sometimes', 'integer', 'min:1'],
|
||||
|
||||
// Product data
|
||||
'product_code' => ['required', 'string', 'max:50', 'regex:/^[A-Z0-9_-]+$/'],
|
||||
'product_name' => ['required', 'string', 'max:100'],
|
||||
'category_id' => ['sometimes', 'integer', 'min:1'],
|
||||
'description' => ['nullable', 'string', 'max:1000'],
|
||||
|
||||
// Product attributes
|
||||
'unit' => ['nullable', 'string', 'max:20'],
|
||||
'min_order_qty' => ['nullable', 'numeric', 'min:0'],
|
||||
'lead_time_days' => ['nullable', 'integer', 'min:0'],
|
||||
'is_active' => ['boolean'],
|
||||
|
||||
// Additional options
|
||||
'create_bom_items' => ['boolean'],
|
||||
'validate_bom' => ['boolean'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'product_code.regex' => __('validation.product.code_format'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'model_id' => __('validation.attributes.model_id'),
|
||||
'input_parameters' => __('validation.attributes.input_parameters'),
|
||||
'bom_template_id' => __('validation.attributes.bom_template_id'),
|
||||
'product_code' => __('validation.attributes.product_code'),
|
||||
'product_name' => __('validation.attributes.product_name'),
|
||||
'category_id' => __('validation.attributes.category_id'),
|
||||
'description' => __('validation.attributes.description'),
|
||||
'unit' => __('validation.attributes.unit'),
|
||||
'min_order_qty' => __('validation.attributes.min_order_qty'),
|
||||
'lead_time_days' => __('validation.attributes.lead_time_days'),
|
||||
'is_active' => __('validation.attributes.is_active'),
|
||||
'create_bom_items' => __('validation.attributes.create_bom_items'),
|
||||
'validate_bom' => __('validation.attributes.validate_bom'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'is_active' => $this->boolean('is_active', true),
|
||||
'create_bom_items' => $this->boolean('create_bom_items', true),
|
||||
'validate_bom' => $this->boolean('validate_bom', true),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\BomResolver;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ResolvePreviewRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'input_parameters' => ['required', 'array', 'min:1'],
|
||||
'input_parameters.*' => ['required'],
|
||||
'bom_template_id' => ['sometimes', 'integer', 'min:1'],
|
||||
'include_calculated_values' => ['boolean'],
|
||||
'include_bom_items' => ['boolean'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'input_parameters' => __('validation.attributes.input_parameters'),
|
||||
'bom_template_id' => __('validation.attributes.bom_template_id'),
|
||||
'include_calculated_values' => __('validation.attributes.include_calculated_values'),
|
||||
'include_bom_items' => __('validation.attributes.include_bom_items'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'include_calculated_values' => $this->boolean('include_calculated_values', true),
|
||||
'include_bom_items' => $this->boolean('include_bom_items', true),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,296 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\Design;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use App\Models\Design\ModelParameter;
|
||||
use App\Models\Product;
|
||||
use App\Models\Material;
|
||||
|
||||
class BomConditionRuleFormRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Authorization handled by middleware
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$modelId = $this->route('modelId');
|
||||
$ruleId = $this->route('ruleId');
|
||||
|
||||
$rules = [
|
||||
'rule_name' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:100',
|
||||
Rule::unique('bom_condition_rules')
|
||||
->where('model_id', $modelId)
|
||||
->where('tenant_id', auth()->user()?->currentTenant?->id)
|
||||
->whereNull('deleted_at')
|
||||
],
|
||||
'condition_expression' => ['required', 'string', 'max:1000'],
|
||||
'action_type' => ['required', 'string', 'in:INCLUDE,EXCLUDE,MODIFY_QUANTITY'],
|
||||
'target_type' => ['required', 'string', 'in:MATERIAL,PRODUCT'],
|
||||
'target_id' => ['required', 'integer', 'min:1'],
|
||||
'quantity_multiplier' => ['nullable', 'numeric', 'min:0'],
|
||||
'is_active' => ['boolean'],
|
||||
'priority' => ['integer', 'min:0'],
|
||||
'description' => ['nullable', 'string', 'max:500'],
|
||||
];
|
||||
|
||||
// For update requests, ignore current record in unique validation
|
||||
if ($ruleId) {
|
||||
$rules['rule_name'][3] = $rules['rule_name'][3]->ignore($ruleId);
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'rule_name.required' => '규칙 이름은 필수입니다.',
|
||||
'rule_name.unique' => '해당 모델에 이미 동일한 규칙 이름이 존재합니다.',
|
||||
'condition_expression.required' => '조건 표현식은 필수입니다.',
|
||||
'condition_expression.max' => '조건 표현식은 1000자를 초과할 수 없습니다.',
|
||||
'action_type.required' => '액션 타입은 필수입니다.',
|
||||
'action_type.in' => '액션 타입은 INCLUDE, EXCLUDE, MODIFY_QUANTITY 중 하나여야 합니다.',
|
||||
'target_type.required' => '대상 타입은 필수입니다.',
|
||||
'target_type.in' => '대상 타입은 MATERIAL 또는 PRODUCT여야 합니다.',
|
||||
'target_id.required' => '대상 ID는 필수입니다.',
|
||||
'target_id.min' => '대상 ID는 1 이상이어야 합니다.',
|
||||
'quantity_multiplier.numeric' => '수량 배수는 숫자여야 합니다.',
|
||||
'quantity_multiplier.min' => '수량 배수는 0 이상이어야 합니다.',
|
||||
'priority.min' => '우선순위는 0 이상이어야 합니다.',
|
||||
'description.max' => '설명은 500자를 초과할 수 없습니다.',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'rule_name' => '규칙 이름',
|
||||
'condition_expression' => '조건 표현식',
|
||||
'action_type' => '액션 타입',
|
||||
'target_type' => '대상 타입',
|
||||
'target_id' => '대상 ID',
|
||||
'quantity_multiplier' => '수량 배수',
|
||||
'is_active' => '활성 상태',
|
||||
'priority' => '우선순위',
|
||||
'description' => '설명',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'is_active' => $this->boolean('is_active', true),
|
||||
'priority' => $this->integer('priority', 0),
|
||||
]);
|
||||
|
||||
// Set default quantity_multiplier for actions that require it
|
||||
if ($this->input('action_type') === 'MODIFY_QUANTITY' && !$this->has('quantity_multiplier')) {
|
||||
$this->merge(['quantity_multiplier' => 1.0]);
|
||||
}
|
||||
|
||||
// Clean up condition expression
|
||||
if ($this->has('condition_expression')) {
|
||||
$expression = preg_replace('/\s+/', ' ', trim($this->input('condition_expression')));
|
||||
$this->merge(['condition_expression' => $expression]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*/
|
||||
public function withValidator($validator)
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
$this->validateConditionExpression($validator);
|
||||
$this->validateTargetExists($validator);
|
||||
$this->validateActionRequirements($validator);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate condition expression syntax and variables.
|
||||
*/
|
||||
private function validateConditionExpression($validator): void
|
||||
{
|
||||
$expression = $this->input('condition_expression');
|
||||
|
||||
if (!$expression) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for potentially dangerous characters or functions
|
||||
$dangerousPatterns = [
|
||||
'/\b(eval|exec|system|shell_exec|passthru|file_get_contents|file_put_contents|fopen|fwrite)\b/i',
|
||||
'/[;{}]/', // Semicolons and braces
|
||||
'/\$[a-zA-Z_]/', // PHP variables
|
||||
'/\bfunction\s*\(/i', // Function definitions
|
||||
];
|
||||
|
||||
foreach ($dangerousPatterns as $pattern) {
|
||||
if (preg_match($pattern, $expression)) {
|
||||
$validator->errors()->add('condition_expression', '조건 표현식에 허용되지 않는 문자나 함수가 포함되어 있습니다.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate condition expression format
|
||||
if (!$this->isValidConditionExpression($expression)) {
|
||||
$validator->errors()->add('condition_expression', '조건 표현식의 형식이 올바르지 않습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate variables in expression exist as parameters
|
||||
$this->validateConditionVariables($validator, $expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if condition expression has valid syntax.
|
||||
*/
|
||||
private function isValidConditionExpression(string $expression): bool
|
||||
{
|
||||
// Allow comparison operators, logical operators, variables, numbers, strings
|
||||
$patterns = [
|
||||
'/^.*(==|!=|>=|<=|>|<|\sIN\s|\sNOT\sIN\s|\sAND\s|\sOR\s).*$/i',
|
||||
'/^(true|false|[0-9]+)$/i', // Simple boolean or number
|
||||
];
|
||||
|
||||
foreach ($patterns as $pattern) {
|
||||
if (preg_match($pattern, $expression)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that variables in condition exist as model parameters.
|
||||
*/
|
||||
private function validateConditionVariables($validator, string $expression): void
|
||||
{
|
||||
$modelId = $this->route('modelId');
|
||||
|
||||
// Extract variable names from expression (exclude operators and values)
|
||||
preg_match_all('/\b[a-zA-Z][a-zA-Z0-9_]*\b/', $expression, $matches);
|
||||
$variables = $matches[0];
|
||||
|
||||
// Remove logical operators and reserved words
|
||||
$reservedWords = ['AND', 'OR', 'IN', 'NOT', 'TRUE', 'FALSE', 'true', 'false'];
|
||||
$variables = array_diff($variables, $reservedWords);
|
||||
|
||||
if (empty($variables)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get existing parameters for this model
|
||||
$existingParameters = ModelParameter::where('model_id', $modelId)
|
||||
->where('tenant_id', auth()->user()?->currentTenant?->id)
|
||||
->whereNull('deleted_at')
|
||||
->pluck('parameter_name')
|
||||
->toArray();
|
||||
|
||||
// Check for undefined variables
|
||||
$undefinedVariables = array_diff($variables, $existingParameters);
|
||||
|
||||
if (!empty($undefinedVariables)) {
|
||||
$validator->errors()->add('condition_expression',
|
||||
'조건식에 정의되지 않은 변수가 사용되었습니다: ' . implode(', ', $undefinedVariables)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the target (MATERIAL or PRODUCT) exists.
|
||||
*/
|
||||
private function validateTargetExists($validator): void
|
||||
{
|
||||
$targetType = $this->input('target_type');
|
||||
$targetId = $this->input('target_id');
|
||||
|
||||
if (!$targetType || !$targetId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tenantId = auth()->user()?->currentTenant?->id;
|
||||
|
||||
switch ($targetType) {
|
||||
case 'MATERIAL':
|
||||
$exists = Material::where('id', $targetId)
|
||||
->where('tenant_id', $tenantId)
|
||||
->whereNull('deleted_at')
|
||||
->exists();
|
||||
|
||||
if (!$exists) {
|
||||
$validator->errors()->add('target_id', '지정된 자재가 존재하지 않습니다.');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'PRODUCT':
|
||||
$exists = Product::where('id', $targetId)
|
||||
->where('tenant_id', $tenantId)
|
||||
->whereNull('deleted_at')
|
||||
->exists();
|
||||
|
||||
if (!$exists) {
|
||||
$validator->errors()->add('target_id', '지정된 제품이 존재하지 않습니다.');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate action-specific requirements.
|
||||
*/
|
||||
private function validateActionRequirements($validator): void
|
||||
{
|
||||
$actionType = $this->input('action_type');
|
||||
$quantityMultiplier = $this->input('quantity_multiplier');
|
||||
|
||||
switch ($actionType) {
|
||||
case 'MODIFY_QUANTITY':
|
||||
// MODIFY_QUANTITY action requires quantity_multiplier
|
||||
if ($quantityMultiplier === null || $quantityMultiplier === '') {
|
||||
$validator->errors()->add('quantity_multiplier', 'MODIFY_QUANTITY 액션에는 수량 배수가 필요합니다.');
|
||||
} elseif ($quantityMultiplier <= 0) {
|
||||
$validator->errors()->add('quantity_multiplier', 'MODIFY_QUANTITY 액션의 수량 배수는 0보다 커야 합니다.');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'INCLUDE':
|
||||
// INCLUDE action can optionally have quantity_multiplier (default to 1)
|
||||
if ($quantityMultiplier !== null && $quantityMultiplier <= 0) {
|
||||
$validator->errors()->add('quantity_multiplier', 'INCLUDE 액션의 수량 배수는 0보다 커야 합니다.');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'EXCLUDE':
|
||||
// EXCLUDE action doesn't need quantity_multiplier
|
||||
if ($quantityMultiplier !== null) {
|
||||
$validator->errors()->add('quantity_multiplier', 'EXCLUDE 액션에는 수량 배수가 필요하지 않습니다.');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,274 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\Design;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use App\Models\Design\ModelParameter;
|
||||
use App\Models\Design\BomTemplate;
|
||||
|
||||
class BomResolverFormRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Authorization handled by middleware
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'input_parameters' => ['required', 'array', 'min:1'],
|
||||
'input_parameters.*' => ['required'],
|
||||
'bom_template_id' => ['sometimes', 'integer', 'min:1'],
|
||||
'include_calculated_values' => ['boolean'],
|
||||
'include_bom_items' => ['boolean'],
|
||||
'include_condition_rules' => ['boolean'],
|
||||
'validate_before_resolve' => ['boolean'],
|
||||
'calculation_precision' => ['integer', 'min:0', 'max:10'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'input_parameters.required' => '입력 매개변수는 필수입니다.',
|
||||
'input_parameters.array' => '입력 매개변수는 배열 형태여야 합니다.',
|
||||
'input_parameters.min' => '최소 하나 이상의 입력 매개변수가 필요합니다.',
|
||||
'input_parameters.*.required' => '모든 입력 매개변수 값은 필수입니다.',
|
||||
'bom_template_id.integer' => 'BOM 템플릿 ID는 정수여야 합니다.',
|
||||
'bom_template_id.min' => 'BOM 템플릿 ID는 1 이상이어야 합니다.',
|
||||
'calculation_precision.integer' => '계산 정밀도는 정수여야 합니다.',
|
||||
'calculation_precision.min' => '계산 정밀도는 0 이상이어야 합니다.',
|
||||
'calculation_precision.max' => '계산 정밀도는 10 이하여야 합니다.',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'input_parameters' => '입력 매개변수',
|
||||
'bom_template_id' => 'BOM 템플릿 ID',
|
||||
'include_calculated_values' => '계산값 포함 여부',
|
||||
'include_bom_items' => 'BOM 아이템 포함 여부',
|
||||
'include_condition_rules' => '조건 규칙 포함 여부',
|
||||
'validate_before_resolve' => '해결 전 유효성 검사',
|
||||
'calculation_precision' => '계산 정밀도',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'include_calculated_values' => $this->boolean('include_calculated_values', true),
|
||||
'include_bom_items' => $this->boolean('include_bom_items', true),
|
||||
'include_condition_rules' => $this->boolean('include_condition_rules', true),
|
||||
'validate_before_resolve' => $this->boolean('validate_before_resolve', true),
|
||||
'calculation_precision' => $this->integer('calculation_precision', 2),
|
||||
]);
|
||||
|
||||
// Ensure input_parameters is an array
|
||||
if ($this->has('input_parameters') && !is_array($this->input('input_parameters'))) {
|
||||
$params = json_decode($this->input('input_parameters'), true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$this->merge(['input_parameters' => $params]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*/
|
||||
public function withValidator($validator)
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
$this->validateInputParameters($validator);
|
||||
$this->validateBomTemplate($validator);
|
||||
$this->validateParameterValues($validator);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate input parameters against model parameter definitions.
|
||||
*/
|
||||
private function validateInputParameters($validator): void
|
||||
{
|
||||
$modelId = $this->route('modelId');
|
||||
$inputParameters = $this->input('input_parameters', []);
|
||||
|
||||
if (empty($inputParameters)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get model's INPUT parameters
|
||||
$modelParameters = ModelParameter::where('model_id', $modelId)
|
||||
->where('parameter_type', 'INPUT')
|
||||
->where('tenant_id', auth()->user()?->currentTenant?->id)
|
||||
->whereNull('deleted_at')
|
||||
->get()
|
||||
->keyBy('parameter_name');
|
||||
|
||||
// Check for required parameters
|
||||
$requiredParams = $modelParameters->where('is_required', true)->pluck('parameter_name')->toArray();
|
||||
$providedParams = array_keys($inputParameters);
|
||||
$missingRequired = array_diff($requiredParams, $providedParams);
|
||||
|
||||
if (!empty($missingRequired)) {
|
||||
$validator->errors()->add('input_parameters',
|
||||
'다음 필수 매개변수가 누락되었습니다: ' . implode(', ', $missingRequired)
|
||||
);
|
||||
}
|
||||
|
||||
// Check for unknown parameters
|
||||
$knownParams = $modelParameters->pluck('parameter_name')->toArray();
|
||||
$unknownParams = array_diff($providedParams, $knownParams);
|
||||
|
||||
if (!empty($unknownParams)) {
|
||||
$validator->errors()->add('input_parameters',
|
||||
'알 수 없는 매개변수가 포함되어 있습니다: ' . implode(', ', $unknownParams)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate BOM template exists and belongs to the model.
|
||||
*/
|
||||
private function validateBomTemplate($validator): void
|
||||
{
|
||||
$bomTemplateId = $this->input('bom_template_id');
|
||||
$modelId = $this->route('modelId');
|
||||
|
||||
if (!$bomTemplateId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$template = BomTemplate::where('id', $bomTemplateId)
|
||||
->where('tenant_id', auth()->user()?->currentTenant?->id)
|
||||
->whereNull('deleted_at')
|
||||
->first();
|
||||
|
||||
if (!$template) {
|
||||
$validator->errors()->add('bom_template_id', '지정된 BOM 템플릿이 존재하지 않습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if template belongs to the model (through model_version)
|
||||
if ($template->modelVersion && $template->modelVersion->model_id != $modelId) {
|
||||
$validator->errors()->add('bom_template_id', 'BOM 템플릿이 해당 모델에 속하지 않습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate parameter values against their constraints.
|
||||
*/
|
||||
private function validateParameterValues($validator): void
|
||||
{
|
||||
$modelId = $this->route('modelId');
|
||||
$inputParameters = $this->input('input_parameters', []);
|
||||
|
||||
if (empty($inputParameters)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get model parameter definitions
|
||||
$modelParameters = ModelParameter::where('model_id', $modelId)
|
||||
->where('parameter_type', 'INPUT')
|
||||
->where('tenant_id', auth()->user()?->currentTenant?->id)
|
||||
->whereNull('deleted_at')
|
||||
->get()
|
||||
->keyBy('parameter_name');
|
||||
|
||||
foreach ($inputParameters as $paramName => $value) {
|
||||
$parameter = $modelParameters->get($paramName);
|
||||
|
||||
if (!$parameter) {
|
||||
continue; // Unknown parameter already handled above
|
||||
}
|
||||
|
||||
// Validate value against parameter constraints
|
||||
$this->validateParameterValue($validator, $parameter, $paramName, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate individual parameter value.
|
||||
*/
|
||||
private function validateParameterValue($validator, $parameter, string $paramName, $value): void
|
||||
{
|
||||
// Check for null/empty required values
|
||||
if ($parameter->is_required && ($value === null || $value === '')) {
|
||||
$validator->errors()->add("input_parameters.{$paramName}", "{$paramName}은(는) 필수 매개변수입니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate data type
|
||||
switch ($parameter->data_type ?? 'STRING') {
|
||||
case 'INTEGER':
|
||||
if (!is_numeric($value) || (int)$value != $value) {
|
||||
$validator->errors()->add("input_parameters.{$paramName}", "{$paramName}은(는) 정수여야 합니다.");
|
||||
return;
|
||||
}
|
||||
$value = (int)$value;
|
||||
break;
|
||||
|
||||
case 'DECIMAL':
|
||||
if (!is_numeric($value)) {
|
||||
$validator->errors()->add("input_parameters.{$paramName}", "{$paramName}은(는) 숫자여야 합니다.");
|
||||
return;
|
||||
}
|
||||
$value = (float)$value;
|
||||
break;
|
||||
|
||||
case 'BOOLEAN':
|
||||
if (!is_bool($value) && !in_array($value, [0, 1, '0', '1', 'true', 'false'])) {
|
||||
$validator->errors()->add("input_parameters.{$paramName}", "{$paramName}은(는) 불린 값이어야 합니다.");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'STRING':
|
||||
if (!is_string($value) && !is_numeric($value)) {
|
||||
$validator->errors()->add("input_parameters.{$paramName}", "{$paramName}은(는) 문자열이어야 합니다.");
|
||||
return;
|
||||
}
|
||||
$value = (string)$value;
|
||||
break;
|
||||
}
|
||||
|
||||
// Validate min/max values for numeric types
|
||||
if (in_array($parameter->data_type, ['INTEGER', 'DECIMAL']) && is_numeric($value)) {
|
||||
if ($parameter->min_value !== null && $value < $parameter->min_value) {
|
||||
$validator->errors()->add("input_parameters.{$paramName}",
|
||||
"{$paramName}은(는) {$parameter->min_value} 이상이어야 합니다."
|
||||
);
|
||||
}
|
||||
|
||||
if ($parameter->max_value !== null && $value > $parameter->max_value) {
|
||||
$validator->errors()->add("input_parameters.{$paramName}",
|
||||
"{$paramName}은(는) {$parameter->max_value} 이하여야 합니다."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate options for select type parameters
|
||||
if (!empty($parameter->options) && !in_array($value, $parameter->options)) {
|
||||
$validOptions = implode(', ', $parameter->options);
|
||||
$validator->errors()->add("input_parameters.{$paramName}",
|
||||
"{$paramName}의 값은 다음 중 하나여야 합니다: {$validOptions}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,264 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\Design;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use App\Models\Design\ModelParameter;
|
||||
|
||||
class ModelFormulaFormRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Authorization handled by middleware
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$modelId = $this->route('modelId');
|
||||
$formulaId = $this->route('formulaId');
|
||||
|
||||
$rules = [
|
||||
'formula_name' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:100',
|
||||
'regex:/^[a-zA-Z][a-zA-Z0-9_\s]*$/',
|
||||
Rule::unique('model_formulas')
|
||||
->where('model_id', $modelId)
|
||||
->where('tenant_id', auth()->user()?->currentTenant?->id)
|
||||
->whereNull('deleted_at')
|
||||
],
|
||||
'formula_expression' => ['required', 'string', 'max:1000'],
|
||||
'unit' => ['nullable', 'string', 'max:20'],
|
||||
'description' => ['nullable', 'string', 'max:500'],
|
||||
'calculation_order' => ['integer', 'min:0'],
|
||||
'dependencies' => ['nullable', 'array'],
|
||||
'dependencies.*' => ['string', 'max:50', 'regex:/^[a-zA-Z][a-zA-Z0-9_]*$/'],
|
||||
];
|
||||
|
||||
// For update requests, ignore current record in unique validation
|
||||
if ($formulaId) {
|
||||
$rules['formula_name'][4] = $rules['formula_name'][4]->ignore($formulaId);
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'formula_name.required' => '공식 이름은 필수입니다.',
|
||||
'formula_name.regex' => '공식 이름은 영문자로 시작하고 영문자, 숫자, 언더스코어, 공백만 사용할 수 있습니다.',
|
||||
'formula_name.unique' => '해당 모델에 이미 동일한 공식 이름이 존재합니다.',
|
||||
'formula_expression.required' => '공식 표현식은 필수입니다.',
|
||||
'formula_expression.max' => '공식 표현식은 1000자를 초과할 수 없습니다.',
|
||||
'unit.max' => '단위는 20자를 초과할 수 없습니다.',
|
||||
'description.max' => '설명은 500자를 초과할 수 없습니다.',
|
||||
'calculation_order.min' => '계산 순서는 0 이상이어야 합니다.',
|
||||
'dependencies.array' => '의존성은 배열 형태여야 합니다.',
|
||||
'dependencies.*.regex' => '의존성 변수명은 영문자로 시작하고 영문자, 숫자, 언더스코어만 사용할 수 있습니다.',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'formula_name' => '공식 이름',
|
||||
'formula_expression' => '공식 표현식',
|
||||
'unit' => '단위',
|
||||
'description' => '설명',
|
||||
'calculation_order' => '계산 순서',
|
||||
'dependencies' => '의존성',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'calculation_order' => $this->integer('calculation_order', 0),
|
||||
]);
|
||||
|
||||
// Convert dependencies to array if it's a string
|
||||
if ($this->has('dependencies') && is_string($this->input('dependencies'))) {
|
||||
$dependencies = json_decode($this->input('dependencies'), true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$this->merge(['dependencies' => $dependencies]);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up formula expression - remove extra whitespace
|
||||
if ($this->has('formula_expression')) {
|
||||
$expression = preg_replace('/\s+/', ' ', trim($this->input('formula_expression')));
|
||||
$this->merge(['formula_expression' => $expression]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*/
|
||||
public function withValidator($validator)
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
$this->validateFormulaExpression($validator);
|
||||
$this->validateDependencies($validator);
|
||||
$this->validateNoCircularDependency($validator);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate formula expression syntax.
|
||||
*/
|
||||
private function validateFormulaExpression($validator): void
|
||||
{
|
||||
$expression = $this->input('formula_expression');
|
||||
|
||||
if (!$expression) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for potentially dangerous characters or functions
|
||||
$dangerousPatterns = [
|
||||
'/\b(eval|exec|system|shell_exec|passthru|file_get_contents|file_put_contents|fopen|fwrite)\b/i',
|
||||
'/[;{}]/', // Semicolons and braces
|
||||
'/\$[a-zA-Z_]/', // PHP variables
|
||||
'/\bfunction\s*\(/i', // Function definitions
|
||||
];
|
||||
|
||||
foreach ($dangerousPatterns as $pattern) {
|
||||
if (preg_match($pattern, $expression)) {
|
||||
$validator->errors()->add('formula_expression', '공식 표현식에 허용되지 않는 문자나 함수가 포함되어 있습니다.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate mathematical expression format
|
||||
if (!$this->isValidMathExpression($expression)) {
|
||||
$validator->errors()->add('formula_expression', '공식 표현식의 형식이 올바르지 않습니다. 수학 연산자와 변수명만 사용할 수 있습니다.');
|
||||
}
|
||||
|
||||
// Extract variables from expression and validate they exist as parameters
|
||||
$this->validateExpressionVariables($validator, $expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if expression contains valid mathematical operations.
|
||||
*/
|
||||
private function isValidMathExpression(string $expression): bool
|
||||
{
|
||||
// Allow: numbers, variables, basic math operators, parentheses, math functions
|
||||
$allowedPattern = '/^[a-zA-Z0-9_\s\+\-\*\/\(\)\.\,]+$/';
|
||||
|
||||
// Allow common math functions
|
||||
$mathFunctions = ['sin', 'cos', 'tan', 'log', 'exp', 'sqrt', 'pow', 'abs', 'ceil', 'floor', 'round', 'max', 'min'];
|
||||
$functionPattern = '/\b(' . implode('|', $mathFunctions) . ')\s*\(/i';
|
||||
|
||||
// Remove math functions for basic pattern check
|
||||
$cleanExpression = preg_replace($functionPattern, '', $expression);
|
||||
|
||||
return preg_match($allowedPattern, $cleanExpression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that variables in expression exist as model parameters.
|
||||
*/
|
||||
private function validateExpressionVariables($validator, string $expression): void
|
||||
{
|
||||
$modelId = $this->route('modelId');
|
||||
|
||||
// Extract variable names from expression
|
||||
preg_match_all('/\b[a-zA-Z][a-zA-Z0-9_]*\b/', $expression, $matches);
|
||||
$variables = $matches[0];
|
||||
|
||||
// Remove math functions from variables
|
||||
$mathFunctions = ['sin', 'cos', 'tan', 'log', 'exp', 'sqrt', 'pow', 'abs', 'ceil', 'floor', 'round', 'max', 'min'];
|
||||
$variables = array_diff($variables, $mathFunctions);
|
||||
|
||||
if (empty($variables)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get existing parameters for this model
|
||||
$existingParameters = ModelParameter::where('model_id', $modelId)
|
||||
->where('tenant_id', auth()->user()?->currentTenant?->id)
|
||||
->whereNull('deleted_at')
|
||||
->pluck('parameter_name')
|
||||
->toArray();
|
||||
|
||||
// Check for undefined variables
|
||||
$undefinedVariables = array_diff($variables, $existingParameters);
|
||||
|
||||
if (!empty($undefinedVariables)) {
|
||||
$validator->errors()->add('formula_expression',
|
||||
'공식에 정의되지 않은 변수가 사용되었습니다: ' . implode(', ', $undefinedVariables)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate dependencies array.
|
||||
*/
|
||||
private function validateDependencies($validator): void
|
||||
{
|
||||
$dependencies = $this->input('dependencies', []);
|
||||
$modelId = $this->route('modelId');
|
||||
|
||||
if (empty($dependencies)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get existing parameters for this model
|
||||
$existingParameters = ModelParameter::where('model_id', $modelId)
|
||||
->where('tenant_id', auth()->user()?->currentTenant?->id)
|
||||
->whereNull('deleted_at')
|
||||
->pluck('parameter_name')
|
||||
->toArray();
|
||||
|
||||
// Check that all dependencies exist as parameters
|
||||
$invalidDependencies = array_diff($dependencies, $existingParameters);
|
||||
|
||||
if (!empty($invalidDependencies)) {
|
||||
$validator->errors()->add('dependencies',
|
||||
'존재하지 않는 매개변수가 의존성에 포함되어 있습니다: ' . implode(', ', $invalidDependencies)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate there's no circular dependency.
|
||||
*/
|
||||
private function validateNoCircularDependency($validator): void
|
||||
{
|
||||
$dependencies = $this->input('dependencies', []);
|
||||
$formulaName = $this->input('formula_name');
|
||||
|
||||
if (empty($dependencies) || !$formulaName) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for direct self-reference
|
||||
if (in_array($formulaName, $dependencies)) {
|
||||
$validator->errors()->add('dependencies', '공식이 자기 자신을 참조할 수 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
// For more complex circular dependency check, this would require
|
||||
// analyzing all formulas in the model - simplified version here
|
||||
// In production, implement full dependency graph analysis
|
||||
}
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\Design;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class ModelParameterFormRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Authorization handled by middleware
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$modelId = $this->route('modelId');
|
||||
$parameterId = $this->route('parameterId');
|
||||
|
||||
$rules = [
|
||||
'parameter_name' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:50',
|
||||
'regex:/^[a-zA-Z][a-zA-Z0-9_]*$/',
|
||||
Rule::unique('model_parameters')
|
||||
->where('model_id', $modelId)
|
||||
->where('tenant_id', auth()->user()?->currentTenant?->id)
|
||||
->whereNull('deleted_at')
|
||||
],
|
||||
'parameter_type' => ['required', 'string', 'in:INPUT,OUTPUT'],
|
||||
'is_required' => ['boolean'],
|
||||
'default_value' => ['nullable', 'string', 'max:255'],
|
||||
'min_value' => ['nullable', 'numeric'],
|
||||
'max_value' => ['nullable', 'numeric', 'gte:min_value'],
|
||||
'unit' => ['nullable', 'string', 'max:20'],
|
||||
'options' => ['nullable', 'array'],
|
||||
'options.*' => ['string', 'max:100'],
|
||||
'description' => ['nullable', 'string', 'max:500'],
|
||||
'sort_order' => ['integer', 'min:0'],
|
||||
];
|
||||
|
||||
// For update requests, ignore current record in unique validation
|
||||
if ($parameterId) {
|
||||
$rules['parameter_name'][4] = $rules['parameter_name'][4]->ignore($parameterId);
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'parameter_name.required' => '매개변수 이름은 필수입니다.',
|
||||
'parameter_name.regex' => '매개변수 이름은 영문자로 시작하고 영문자, 숫자, 언더스코어만 사용할 수 있습니다.',
|
||||
'parameter_name.unique' => '해당 모델에 이미 동일한 매개변수 이름이 존재합니다.',
|
||||
'parameter_type.required' => '매개변수 타입은 필수입니다.',
|
||||
'parameter_type.in' => '매개변수 타입은 INPUT 또는 OUTPUT이어야 합니다.',
|
||||
'min_value.numeric' => '최소값은 숫자여야 합니다.',
|
||||
'max_value.numeric' => '최대값은 숫자여야 합니다.',
|
||||
'max_value.gte' => '최대값은 최소값보다 크거나 같아야 합니다.',
|
||||
'unit.max' => '단위는 20자를 초과할 수 없습니다.',
|
||||
'description.max' => '설명은 500자를 초과할 수 없습니다.',
|
||||
'sort_order.min' => '정렬 순서는 0 이상이어야 합니다.',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'parameter_name' => '매개변수 이름',
|
||||
'parameter_type' => '매개변수 타입',
|
||||
'is_required' => '필수 여부',
|
||||
'default_value' => '기본값',
|
||||
'min_value' => '최소값',
|
||||
'max_value' => '최대값',
|
||||
'unit' => '단위',
|
||||
'options' => '옵션 목록',
|
||||
'description' => '설명',
|
||||
'sort_order' => '정렬 순서',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'is_required' => $this->boolean('is_required', false),
|
||||
'sort_order' => $this->integer('sort_order', 0),
|
||||
]);
|
||||
|
||||
// Convert options to array if it's a string
|
||||
if ($this->has('options') && is_string($this->input('options'))) {
|
||||
$options = json_decode($this->input('options'), true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$this->merge(['options' => $options]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*/
|
||||
public function withValidator($validator)
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
// Validate that INPUT parameters can have default values and constraints
|
||||
if ($this->input('parameter_type') === 'INPUT') {
|
||||
$this->validateInputParameterConstraints($validator);
|
||||
}
|
||||
|
||||
// Validate that OUTPUT parameters don't have input-specific fields
|
||||
if ($this->input('parameter_type') === 'OUTPUT') {
|
||||
$this->validateOutputParameterConstraints($validator);
|
||||
}
|
||||
|
||||
// Validate min/max value relationship
|
||||
$this->validateMinMaxValues($validator);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate INPUT parameter specific constraints.
|
||||
*/
|
||||
private function validateInputParameterConstraints($validator): void
|
||||
{
|
||||
// INPUT parameters can have all constraints
|
||||
// No additional validation needed for INPUT type
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate OUTPUT parameter specific constraints.
|
||||
*/
|
||||
private function validateOutputParameterConstraints($validator): void
|
||||
{
|
||||
// OUTPUT parameters should not have certain input-specific fields
|
||||
if ($this->filled('is_required') && $this->input('is_required')) {
|
||||
$validator->errors()->add('is_required', 'OUTPUT 매개변수는 필수 항목이 될 수 없습니다.');
|
||||
}
|
||||
|
||||
if ($this->filled('default_value')) {
|
||||
$validator->errors()->add('default_value', 'OUTPUT 매개변수는 기본값을 가질 수 없습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate min/max value relationship.
|
||||
*/
|
||||
private function validateMinMaxValues($validator): void
|
||||
{
|
||||
$minValue = $this->input('min_value');
|
||||
$maxValue = $this->input('max_value');
|
||||
|
||||
if ($minValue !== null && $maxValue !== null && $minValue > $maxValue) {
|
||||
$validator->errors()->add('max_value', '최대값은 최소값보다 크거나 같아야 합니다.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,388 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\Design;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use App\Models\Design\ModelParameter;
|
||||
use App\Models\Design\BomTemplate;
|
||||
use App\Models\Category;
|
||||
use App\Models\Product;
|
||||
|
||||
class ProductFromModelFormRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Authorization handled by middleware
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$tenantId = auth()->user()?->currentTenant?->id;
|
||||
|
||||
return [
|
||||
// Model and BOM configuration
|
||||
'input_parameters' => ['required', 'array', 'min:1'],
|
||||
'input_parameters.*' => ['required'],
|
||||
'bom_template_id' => ['sometimes', 'integer', 'min:1'],
|
||||
|
||||
// Product basic information
|
||||
'product_code' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:50',
|
||||
'regex:/^[A-Z0-9_-]+$/',
|
||||
Rule::unique('products')
|
||||
->where('tenant_id', $tenantId)
|
||||
->whereNull('deleted_at')
|
||||
],
|
||||
'product_name' => ['required', 'string', 'max:100'],
|
||||
'description' => ['nullable', 'string', 'max:1000'],
|
||||
|
||||
// Product categorization
|
||||
'category_id' => ['sometimes', 'integer', 'min:1'],
|
||||
'product_type' => ['nullable', 'string', 'in:PRODUCT,PART,SUBASSEMBLY'],
|
||||
|
||||
// Product specifications
|
||||
'unit' => ['nullable', 'string', 'max:20'],
|
||||
'min_order_qty' => ['nullable', 'numeric', 'min:0'],
|
||||
'lead_time_days' => ['nullable', 'integer', 'min:0', 'max:365'],
|
||||
'is_active' => ['boolean'],
|
||||
|
||||
// Product creation options
|
||||
'create_bom_items' => ['boolean'],
|
||||
'validate_bom' => ['boolean'],
|
||||
'save_parameters' => ['boolean'],
|
||||
'auto_generate_variants' => ['boolean'],
|
||||
|
||||
// Pricing and cost
|
||||
'base_cost' => ['nullable', 'numeric', 'min:0'],
|
||||
'markup_percentage' => ['nullable', 'numeric', 'min:0', 'max:1000'],
|
||||
|
||||
// Additional attributes (dynamic based on category)
|
||||
'attributes' => ['sometimes', 'array'],
|
||||
'attributes.*' => ['nullable'],
|
||||
|
||||
// Tags and classification
|
||||
'tags' => ['sometimes', 'array'],
|
||||
'tags.*' => ['string', 'max:50'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'input_parameters.required' => '입력 매개변수는 필수입니다.',
|
||||
'input_parameters.array' => '입력 매개변수는 배열 형태여야 합니다.',
|
||||
'input_parameters.min' => '최소 하나 이상의 입력 매개변수가 필요합니다.',
|
||||
'input_parameters.*.required' => '모든 입력 매개변수 값은 필수입니다.',
|
||||
|
||||
'product_code.required' => '제품 코드는 필수입니다.',
|
||||
'product_code.regex' => '제품 코드는 대문자, 숫자, 언더스코어, 하이픈만 사용할 수 있습니다.',
|
||||
'product_code.unique' => '이미 존재하는 제품 코드입니다.',
|
||||
|
||||
'product_name.required' => '제품명은 필수입니다.',
|
||||
'product_name.max' => '제품명은 100자를 초과할 수 없습니다.',
|
||||
|
||||
'description.max' => '설명은 1000자를 초과할 수 없습니다.',
|
||||
|
||||
'category_id.integer' => '카테고리 ID는 정수여야 합니다.',
|
||||
'category_id.min' => '카테고리 ID는 1 이상이어야 합니다.',
|
||||
|
||||
'product_type.in' => '제품 타입은 PRODUCT, PART, SUBASSEMBLY 중 하나여야 합니다.',
|
||||
|
||||
'unit.max' => '단위는 20자를 초과할 수 없습니다.',
|
||||
'min_order_qty.numeric' => '최소 주문 수량은 숫자여야 합니다.',
|
||||
'min_order_qty.min' => '최소 주문 수량은 0 이상이어야 합니다.',
|
||||
|
||||
'lead_time_days.integer' => '리드타임은 정수여야 합니다.',
|
||||
'lead_time_days.min' => '리드타임은 0 이상이어야 합니다.',
|
||||
'lead_time_days.max' => '리드타임은 365일을 초과할 수 없습니다.',
|
||||
|
||||
'base_cost.numeric' => '기본 원가는 숫자여야 합니다.',
|
||||
'base_cost.min' => '기본 원가는 0 이상이어야 합니다.',
|
||||
|
||||
'markup_percentage.numeric' => '마크업 비율은 숫자여야 합니다.',
|
||||
'markup_percentage.min' => '마크업 비율은 0 이상이어야 합니다.',
|
||||
'markup_percentage.max' => '마크업 비율은 1000%를 초과할 수 없습니다.',
|
||||
|
||||
'bom_template_id.integer' => 'BOM 템플릿 ID는 정수여야 합니다.',
|
||||
'bom_template_id.min' => 'BOM 템플릿 ID는 1 이상이어야 합니다.',
|
||||
|
||||
'tags.array' => '태그는 배열 형태여야 합니다.',
|
||||
'tags.*.max' => '각 태그는 50자를 초과할 수 없습니다.',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'input_parameters' => '입력 매개변수',
|
||||
'bom_template_id' => 'BOM 템플릿 ID',
|
||||
'product_code' => '제품 코드',
|
||||
'product_name' => '제품명',
|
||||
'description' => '설명',
|
||||
'category_id' => '카테고리 ID',
|
||||
'product_type' => '제품 타입',
|
||||
'unit' => '단위',
|
||||
'min_order_qty' => '최소 주문 수량',
|
||||
'lead_time_days' => '리드타임',
|
||||
'is_active' => '활성 상태',
|
||||
'create_bom_items' => 'BOM 아이템 생성',
|
||||
'validate_bom' => 'BOM 유효성 검사',
|
||||
'save_parameters' => '매개변수 저장',
|
||||
'auto_generate_variants' => '자동 변형 생성',
|
||||
'base_cost' => '기본 원가',
|
||||
'markup_percentage' => '마크업 비율',
|
||||
'attributes' => '추가 속성',
|
||||
'tags' => '태그',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'is_active' => $this->boolean('is_active', true),
|
||||
'create_bom_items' => $this->boolean('create_bom_items', true),
|
||||
'validate_bom' => $this->boolean('validate_bom', true),
|
||||
'save_parameters' => $this->boolean('save_parameters', true),
|
||||
'auto_generate_variants' => $this->boolean('auto_generate_variants', false),
|
||||
]);
|
||||
|
||||
// Ensure input_parameters is an array
|
||||
if ($this->has('input_parameters') && !is_array($this->input('input_parameters'))) {
|
||||
$params = json_decode($this->input('input_parameters'), true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$this->merge(['input_parameters' => $params]);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure attributes is an array
|
||||
if ($this->has('attributes') && !is_array($this->input('attributes'))) {
|
||||
$attributes = json_decode($this->input('attributes'), true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$this->merge(['attributes' => $attributes]);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure tags is an array
|
||||
if ($this->has('tags') && !is_array($this->input('tags'))) {
|
||||
$tags = json_decode($this->input('tags'), true);
|
||||
if (json_last_error() === JSON_ERROR_NONE) {
|
||||
$this->merge(['tags' => $tags]);
|
||||
}
|
||||
}
|
||||
|
||||
// Set default product_type if not provided
|
||||
if (!$this->has('product_type')) {
|
||||
$this->merge(['product_type' => 'PRODUCT']);
|
||||
}
|
||||
|
||||
// Convert product_code to uppercase
|
||||
if ($this->has('product_code')) {
|
||||
$this->merge(['product_code' => strtoupper($this->input('product_code'))]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*/
|
||||
public function withValidator($validator)
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
$this->validateInputParameters($validator);
|
||||
$this->validateBomTemplate($validator);
|
||||
$this->validateCategory($validator);
|
||||
$this->validateParameterValues($validator);
|
||||
$this->validateBusinessRules($validator);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate input parameters against model parameter definitions.
|
||||
*/
|
||||
private function validateInputParameters($validator): void
|
||||
{
|
||||
$modelId = $this->route('modelId');
|
||||
$inputParameters = $this->input('input_parameters', []);
|
||||
|
||||
if (empty($inputParameters)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get model's INPUT parameters
|
||||
$modelParameters = ModelParameter::where('model_id', $modelId)
|
||||
->where('parameter_type', 'INPUT')
|
||||
->where('tenant_id', auth()->user()?->currentTenant?->id)
|
||||
->whereNull('deleted_at')
|
||||
->get()
|
||||
->keyBy('parameter_name');
|
||||
|
||||
// Check for required parameters
|
||||
$requiredParams = $modelParameters->where('is_required', true)->pluck('parameter_name')->toArray();
|
||||
$providedParams = array_keys($inputParameters);
|
||||
$missingRequired = array_diff($requiredParams, $providedParams);
|
||||
|
||||
if (!empty($missingRequired)) {
|
||||
$validator->errors()->add('input_parameters',
|
||||
'다음 필수 매개변수가 누락되었습니다: ' . implode(', ', $missingRequired)
|
||||
);
|
||||
}
|
||||
|
||||
// Check for unknown parameters
|
||||
$knownParams = $modelParameters->pluck('parameter_name')->toArray();
|
||||
$unknownParams = array_diff($providedParams, $knownParams);
|
||||
|
||||
if (!empty($unknownParams)) {
|
||||
$validator->errors()->add('input_parameters',
|
||||
'알 수 없는 매개변수가 포함되어 있습니다: ' . implode(', ', $unknownParams)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate BOM template exists and belongs to the model.
|
||||
*/
|
||||
private function validateBomTemplate($validator): void
|
||||
{
|
||||
$bomTemplateId = $this->input('bom_template_id');
|
||||
$modelId = $this->route('modelId');
|
||||
|
||||
if (!$bomTemplateId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$template = BomTemplate::where('id', $bomTemplateId)
|
||||
->where('tenant_id', auth()->user()?->currentTenant?->id)
|
||||
->whereNull('deleted_at')
|
||||
->first();
|
||||
|
||||
if (!$template) {
|
||||
$validator->errors()->add('bom_template_id', '지정된 BOM 템플릿이 존재하지 않습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if template belongs to the model (through model_version)
|
||||
if ($template->modelVersion && $template->modelVersion->model_id != $modelId) {
|
||||
$validator->errors()->add('bom_template_id', 'BOM 템플릿이 해당 모델에 속하지 않습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate category exists and is accessible.
|
||||
*/
|
||||
private function validateCategory($validator): void
|
||||
{
|
||||
$categoryId = $this->input('category_id');
|
||||
|
||||
if (!$categoryId) {
|
||||
return;
|
||||
}
|
||||
|
||||
$category = Category::where('id', $categoryId)
|
||||
->where('tenant_id', auth()->user()?->currentTenant?->id)
|
||||
->whereNull('deleted_at')
|
||||
->first();
|
||||
|
||||
if (!$category) {
|
||||
$validator->errors()->add('category_id', '지정된 카테고리가 존재하지 않습니다.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate parameter values against their constraints.
|
||||
*/
|
||||
private function validateParameterValues($validator): void
|
||||
{
|
||||
$modelId = $this->route('modelId');
|
||||
$inputParameters = $this->input('input_parameters', []);
|
||||
|
||||
if (empty($inputParameters)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get model parameter definitions
|
||||
$modelParameters = ModelParameter::where('model_id', $modelId)
|
||||
->where('parameter_type', 'INPUT')
|
||||
->where('tenant_id', auth()->user()?->currentTenant?->id)
|
||||
->whereNull('deleted_at')
|
||||
->get()
|
||||
->keyBy('parameter_name');
|
||||
|
||||
foreach ($inputParameters as $paramName => $value) {
|
||||
$parameter = $modelParameters->get($paramName);
|
||||
|
||||
if (!$parameter) {
|
||||
continue; // Unknown parameter already handled above
|
||||
}
|
||||
|
||||
// Validate value against parameter constraints
|
||||
$this->validateParameterValue($validator, $parameter, $paramName, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate individual parameter value.
|
||||
*/
|
||||
private function validateParameterValue($validator, $parameter, string $paramName, $value): void
|
||||
{
|
||||
// Check for null/empty required values
|
||||
if ($parameter->is_required && ($value === null || $value === '')) {
|
||||
$validator->errors()->add("input_parameters.{$paramName}", "{$paramName}은(는) 필수 매개변수입니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip validation for empty optional parameters
|
||||
if (!$parameter->is_required && ($value === null || $value === '')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use model's validation method if available
|
||||
if (method_exists($parameter, 'validateValue') && !$parameter->validateValue($value)) {
|
||||
$validator->errors()->add("input_parameters.{$paramName}", "{$paramName}의 값이 유효하지 않습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate business rules specific to product creation.
|
||||
*/
|
||||
private function validateBusinessRules($validator): void
|
||||
{
|
||||
// If validate_bom is true, ensure we have enough data for BOM validation
|
||||
if ($this->input('validate_bom', true) && !$this->input('bom_template_id')) {
|
||||
// Could check if model has default BOM template or condition rules
|
||||
// For now, just warn
|
||||
}
|
||||
|
||||
// If auto_generate_variants is true, validate variant generation is possible
|
||||
if ($this->input('auto_generate_variants', false)) {
|
||||
// Check if model has variant-generating parameters
|
||||
// This would require checking parameter configurations
|
||||
}
|
||||
|
||||
// Validate pricing logic
|
||||
$baseCost = $this->input('base_cost');
|
||||
$markupPercentage = $this->input('markup_percentage');
|
||||
|
||||
if ($baseCost !== null && $markupPercentage !== null) {
|
||||
if ($baseCost == 0 && $markupPercentage > 0) {
|
||||
$validator->errors()->add('markup_percentage', '기본 원가가 0인 경우 마크업을 설정할 수 없습니다.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\ModelFormula;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class CreateModelFormulaRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:100'],
|
||||
'target_parameter' => ['required', 'string', 'max:50', 'regex:/^[a-zA-Z][a-zA-Z0-9_]*$/'],
|
||||
'expression' => ['required', 'string', 'max:1000'],
|
||||
'description' => ['nullable', 'string', 'max:500'],
|
||||
'is_active' => ['boolean'],
|
||||
'execution_order' => ['integer', 'min:0'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'target_parameter.regex' => __('validation.model_formula.target_parameter_format'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => __('validation.attributes.formula_name'),
|
||||
'target_parameter' => __('validation.attributes.target_parameter'),
|
||||
'expression' => __('validation.attributes.formula_expression'),
|
||||
'description' => __('validation.attributes.description'),
|
||||
'is_active' => __('validation.attributes.is_active'),
|
||||
'execution_order' => __('validation.attributes.execution_order'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'is_active' => $this->boolean('is_active', true),
|
||||
'execution_order' => $this->integer('execution_order', 0),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\ModelFormula;
|
||||
|
||||
use App\Http\Requests\Api\V1\PaginateRequest;
|
||||
|
||||
class IndexModelFormulaRequest extends PaginateRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return array_merge(parent::rules(), [
|
||||
'search' => ['sometimes', 'string', 'max:255'],
|
||||
'target_parameter' => ['sometimes', 'string', 'max:50'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return array_merge(parent::attributes(), [
|
||||
'search' => __('validation.attributes.search'),
|
||||
'target_parameter' => __('validation.attributes.target_parameter'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\ModelFormula;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateModelFormulaRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['sometimes', 'string', 'max:100'],
|
||||
'target_parameter' => ['sometimes', 'string', 'max:50', 'regex:/^[a-zA-Z][a-zA-Z0-9_]*$/'],
|
||||
'expression' => ['sometimes', 'string', 'max:1000'],
|
||||
'description' => ['nullable', 'string', 'max:500'],
|
||||
'is_active' => ['sometimes', 'boolean'],
|
||||
'execution_order' => ['sometimes', 'integer', 'min:0'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'target_parameter.regex' => __('validation.model_formula.target_parameter_format'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => __('validation.attributes.formula_name'),
|
||||
'target_parameter' => __('validation.attributes.target_parameter'),
|
||||
'expression' => __('validation.attributes.formula_expression'),
|
||||
'description' => __('validation.attributes.description'),
|
||||
'is_active' => __('validation.attributes.is_active'),
|
||||
'execution_order' => __('validation.attributes.execution_order'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
if ($this->has('is_active')) {
|
||||
$this->merge(['is_active' => $this->boolean('is_active')]);
|
||||
}
|
||||
|
||||
if ($this->has('execution_order')) {
|
||||
$this->merge(['execution_order' => $this->integer('execution_order')]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\ModelParameter;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class CreateModelParameterRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:50', 'regex:/^[a-zA-Z][a-zA-Z0-9_]*$/'],
|
||||
'label' => ['required', 'string', 'max:100'],
|
||||
'type' => ['required', 'string', 'in:INPUT,OUTPUT'],
|
||||
'data_type' => ['required', 'string', 'in:INTEGER,DECIMAL,STRING,BOOLEAN'],
|
||||
'unit' => ['nullable', 'string', 'max:20'],
|
||||
'default_value' => ['nullable', 'string', 'max:255'],
|
||||
'min_value' => ['nullable', 'numeric'],
|
||||
'max_value' => ['nullable', 'numeric', 'gte:min_value'],
|
||||
'enum_values' => ['nullable', 'array'],
|
||||
'enum_values.*' => ['string', 'max:100'],
|
||||
'validation_rules' => ['nullable', 'string', 'max:500'],
|
||||
'description' => ['nullable', 'string', 'max:500'],
|
||||
'is_required' => ['boolean'],
|
||||
'display_order' => ['integer', 'min:0'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'name.regex' => __('validation.model_parameter.name_format'),
|
||||
'max_value.gte' => __('validation.model_parameter.max_value_gte_min'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => __('validation.attributes.parameter_name'),
|
||||
'label' => __('validation.attributes.parameter_label'),
|
||||
'type' => __('validation.attributes.parameter_type'),
|
||||
'data_type' => __('validation.attributes.data_type'),
|
||||
'unit' => __('validation.attributes.unit'),
|
||||
'default_value' => __('validation.attributes.default_value'),
|
||||
'min_value' => __('validation.attributes.min_value'),
|
||||
'max_value' => __('validation.attributes.max_value'),
|
||||
'enum_values' => __('validation.attributes.enum_values'),
|
||||
'validation_rules' => __('validation.attributes.validation_rules'),
|
||||
'description' => __('validation.attributes.description'),
|
||||
'is_required' => __('validation.attributes.is_required'),
|
||||
'display_order' => __('validation.attributes.display_order'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'is_required' => $this->boolean('is_required'),
|
||||
'display_order' => $this->integer('display_order', 0),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\ModelParameter;
|
||||
|
||||
use App\Http\Requests\Api\V1\PaginateRequest;
|
||||
|
||||
class IndexModelParameterRequest extends PaginateRequest
|
||||
{
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return array_merge(parent::rules(), [
|
||||
'search' => ['sometimes', 'string', 'max:255'],
|
||||
'type' => ['sometimes', 'string', 'in:INPUT,OUTPUT'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return array_merge(parent::attributes(), [
|
||||
'search' => __('validation.attributes.search'),
|
||||
'type' => __('validation.attributes.parameter_type'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Api\V1\ModelParameter;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateModelParameterRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['sometimes', 'string', 'max:50', 'regex:/^[a-zA-Z][a-zA-Z0-9_]*$/'],
|
||||
'label' => ['sometimes', 'string', 'max:100'],
|
||||
'type' => ['sometimes', 'string', 'in:INPUT,OUTPUT'],
|
||||
'data_type' => ['sometimes', 'string', 'in:INTEGER,DECIMAL,STRING,BOOLEAN'],
|
||||
'unit' => ['nullable', 'string', 'max:20'],
|
||||
'default_value' => ['nullable', 'string', 'max:255'],
|
||||
'min_value' => ['nullable', 'numeric'],
|
||||
'max_value' => ['nullable', 'numeric', 'gte:min_value'],
|
||||
'enum_values' => ['nullable', 'array'],
|
||||
'enum_values.*' => ['string', 'max:100'],
|
||||
'validation_rules' => ['nullable', 'string', 'max:500'],
|
||||
'description' => ['nullable', 'string', 'max:500'],
|
||||
'is_required' => ['sometimes', 'boolean'],
|
||||
'display_order' => ['sometimes', 'integer', 'min:0'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'name.regex' => __('validation.model_parameter.name_format'),
|
||||
'max_value.gte' => __('validation.model_parameter.max_value_gte_min'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attribute names for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => __('validation.attributes.parameter_name'),
|
||||
'label' => __('validation.attributes.parameter_label'),
|
||||
'type' => __('validation.attributes.parameter_type'),
|
||||
'data_type' => __('validation.attributes.data_type'),
|
||||
'unit' => __('validation.attributes.unit'),
|
||||
'default_value' => __('validation.attributes.default_value'),
|
||||
'min_value' => __('validation.attributes.min_value'),
|
||||
'max_value' => __('validation.attributes.max_value'),
|
||||
'enum_values' => __('validation.attributes.enum_values'),
|
||||
'validation_rules' => __('validation.attributes.validation_rules'),
|
||||
'description' => __('validation.attributes.description'),
|
||||
'is_required' => __('validation.attributes.is_required'),
|
||||
'display_order' => __('validation.attributes.display_order'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
if ($this->has('is_required')) {
|
||||
$this->merge(['is_required' => $this->boolean('is_required')]);
|
||||
}
|
||||
|
||||
if ($this->has('display_order')) {
|
||||
$this->merge(['display_order' => $this->integer('display_order')]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Shared\Models\Products\BomConditionRule;
|
||||
|
||||
class BomConditionRuleRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'model_id' => 'required|integer|exists:models,id',
|
||||
'name' => 'required|string|max:100',
|
||||
'condition_expression' => 'required|string|max:1000',
|
||||
'action' => 'required|string|in:' . implode(',', BomConditionRule::ACTIONS),
|
||||
'target_items' => 'required|array|min:1',
|
||||
'target_items.*.product_id' => 'nullable|integer|exists:products,id',
|
||||
'target_items.*.material_id' => 'nullable|integer|exists:materials,id',
|
||||
'target_items.*.quantity' => 'nullable|numeric|min:0',
|
||||
'target_items.*.waste_rate' => 'nullable|numeric|min:0|max:100',
|
||||
'target_items.*.unit' => 'nullable|string|max:20',
|
||||
'target_items.*.memo' => 'nullable|string|max:200',
|
||||
'priority' => 'nullable|integer|min:1',
|
||||
'description' => 'nullable|string|max:500',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'condition_expression.required' => __('error.condition_expression_required'),
|
||||
'target_items.required' => __('error.target_items_required'),
|
||||
'target_items.min' => __('error.target_items_required'),
|
||||
'target_items.*.quantity.min' => __('error.quantity_must_be_positive'),
|
||||
'target_items.*.waste_rate.max' => __('error.waste_rate_too_high'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*/
|
||||
public function withValidator($validator): void
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
$this->validateRuleNameUnique($validator);
|
||||
$this->validateConditionExpression($validator);
|
||||
$this->validateTargetItems($validator);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 규칙명 중복 검증
|
||||
*/
|
||||
private function validateRuleNameUnique($validator): void
|
||||
{
|
||||
if (!$this->input('model_id') || !$this->input('name')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$query = BomConditionRule::where('model_id', $this->input('model_id'))
|
||||
->where('name', $this->input('name'));
|
||||
|
||||
// 수정 시 자기 자신 제외
|
||||
if ($this->route('id')) {
|
||||
$query->where('id', '!=', $this->route('id'));
|
||||
}
|
||||
|
||||
if ($query->exists()) {
|
||||
$validator->errors()->add('name', __('error.rule_name_duplicate'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 조건식 검증
|
||||
*/
|
||||
private function validateConditionExpression($validator): void
|
||||
{
|
||||
if (!$this->input('condition_expression')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tempRule = new BomConditionRule([
|
||||
'condition_expression' => $this->input('condition_expression'),
|
||||
'model_id' => $this->input('model_id'),
|
||||
]);
|
||||
|
||||
$conditionErrors = $tempRule->validateConditionExpression();
|
||||
|
||||
if (!empty($conditionErrors)) {
|
||||
foreach ($conditionErrors as $error) {
|
||||
$validator->errors()->add('condition_expression', $error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 대상 아이템 검증
|
||||
*/
|
||||
private function validateTargetItems($validator): void
|
||||
{
|
||||
$targetItems = $this->input('target_items', []);
|
||||
$action = $this->input('action');
|
||||
|
||||
foreach ($targetItems as $index => $item) {
|
||||
// 제품 또는 자재 참조 필수
|
||||
if (empty($item['product_id']) && empty($item['material_id'])) {
|
||||
$validator->errors()->add(
|
||||
"target_items.{$index}",
|
||||
__('error.target_item_missing_reference')
|
||||
);
|
||||
}
|
||||
|
||||
// 제품과 자재 동시 참조 불가
|
||||
if (!empty($item['product_id']) && !empty($item['material_id'])) {
|
||||
$validator->errors()->add(
|
||||
"target_items.{$index}",
|
||||
__('error.target_item_multiple_reference')
|
||||
);
|
||||
}
|
||||
|
||||
// REPLACE 액션의 경우 replace_from 필수
|
||||
if ($action === BomConditionRule::ACTION_REPLACE && empty($item['replace_from'])) {
|
||||
$validator->errors()->add(
|
||||
"target_items.{$index}.replace_from",
|
||||
__('error.replace_from_required')
|
||||
);
|
||||
}
|
||||
|
||||
// replace_from 검증
|
||||
if (!empty($item['replace_from'])) {
|
||||
if (empty($item['replace_from']['product_id']) && empty($item['replace_from']['material_id'])) {
|
||||
$validator->errors()->add(
|
||||
"target_items.{$index}.replace_from",
|
||||
__('error.replace_from_missing_reference')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
// JSON 문자열인 경우 배열로 변환
|
||||
if ($this->has('target_items') && is_string($this->input('target_items'))) {
|
||||
$this->merge([
|
||||
'target_items' => json_decode($this->input('target_items'), true) ?? []
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class BomResolveRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'model_id' => 'required|integer|exists:models,id',
|
||||
'parameters' => 'required|array',
|
||||
'parameters.*' => 'required',
|
||||
'preview_only' => 'boolean',
|
||||
'use_cache' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'model_id.required' => __('error.model_id_required'),
|
||||
'model_id.exists' => __('error.model_not_found'),
|
||||
'parameters.required' => __('error.parameters_required'),
|
||||
'parameters.array' => __('error.parameters_must_be_array'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*/
|
||||
public function withValidator($validator): void
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
$this->validateParameters($validator);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 매개변수 검증
|
||||
*/
|
||||
private function validateParameters($validator): void
|
||||
{
|
||||
if (!$this->input('model_id') || !$this->input('parameters')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$parameterService = new \App\Services\ModelParameterService();
|
||||
$errors = $parameterService->validateParameterValues(
|
||||
$this->input('model_id'),
|
||||
$this->input('parameters')
|
||||
);
|
||||
|
||||
if (!empty($errors)) {
|
||||
foreach ($errors as $paramName => $paramErrors) {
|
||||
foreach ($paramErrors as $error) {
|
||||
$validator->errors()->add("parameters.{$paramName}", $error);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$validator->errors()->add('parameters', __('error.parameter_validation_failed'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Shared\Models\Products\ModelFormula;
|
||||
|
||||
class ModelFormulaRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'model_id' => 'required|integer|exists:models,id',
|
||||
'name' => 'required|string|max:50|regex:/^[a-zA-Z][a-zA-Z0-9_]*$/',
|
||||
'label' => 'required|string|max:100',
|
||||
'expression' => 'required|string|max:1000',
|
||||
'unit' => 'nullable|string|max:20',
|
||||
'order' => 'nullable|integer|min:1',
|
||||
'description' => 'nullable|string|max:500',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'name.regex' => __('error.formula_name_format'),
|
||||
'expression.required' => __('error.formula_expression_required'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*/
|
||||
public function withValidator($validator): void
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
$this->validateFormulaNameUnique($validator);
|
||||
$this->validateFormulaExpression($validator);
|
||||
$this->validateDependencies($validator);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 공식명 중복 검증
|
||||
*/
|
||||
private function validateFormulaNameUnique($validator): void
|
||||
{
|
||||
if (!$this->input('model_id') || !$this->input('name')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$query = ModelFormula::where('model_id', $this->input('model_id'))
|
||||
->where('name', $this->input('name'));
|
||||
|
||||
// 수정 시 자기 자신 제외
|
||||
if ($this->route('id')) {
|
||||
$query->where('id', '!=', $this->route('id'));
|
||||
}
|
||||
|
||||
if ($query->exists()) {
|
||||
$validator->errors()->add('name', __('error.formula_name_duplicate'));
|
||||
}
|
||||
|
||||
// 매개변수명과 중복 검증
|
||||
$parameterExists = \Shared\Models\Products\ModelParameter::where('model_id', $this->input('model_id'))
|
||||
->where('name', $this->input('name'))
|
||||
->exists();
|
||||
|
||||
if ($parameterExists) {
|
||||
$validator->errors()->add('name', __('error.formula_name_conflicts_with_parameter'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 공식 표현식 검증
|
||||
*/
|
||||
private function validateFormulaExpression($validator): void
|
||||
{
|
||||
if (!$this->input('expression')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tempFormula = new ModelFormula([
|
||||
'expression' => $this->input('expression'),
|
||||
'model_id' => $this->input('model_id'),
|
||||
]);
|
||||
|
||||
$expressionErrors = $tempFormula->validateExpression();
|
||||
|
||||
if (!empty($expressionErrors)) {
|
||||
foreach ($expressionErrors as $error) {
|
||||
$validator->errors()->add('expression', $error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 의존성 검증
|
||||
*/
|
||||
private function validateDependencies($validator): void
|
||||
{
|
||||
if (!$this->input('model_id') || !$this->input('expression')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tempFormula = new ModelFormula([
|
||||
'expression' => $this->input('expression'),
|
||||
'model_id' => $this->input('model_id'),
|
||||
]);
|
||||
|
||||
$dependencies = $tempFormula->extractVariables();
|
||||
|
||||
if (empty($dependencies)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 매개변수 목록 가져오기
|
||||
$parameters = \Shared\Models\Products\ModelParameter::where('model_id', $this->input('model_id'))
|
||||
->active()
|
||||
->pluck('name')
|
||||
->toArray();
|
||||
|
||||
// 기존 공식 목록 가져오기 (자기 자신 제외)
|
||||
$formulasQuery = ModelFormula::where('model_id', $this->input('model_id'))
|
||||
->active();
|
||||
|
||||
if ($this->route('id')) {
|
||||
$formulasQuery->where('id', '!=', $this->route('id'));
|
||||
}
|
||||
|
||||
$formulas = $formulasQuery->pluck('name')->toArray();
|
||||
|
||||
$validNames = array_merge($parameters, $formulas);
|
||||
|
||||
foreach ($dependencies as $dep) {
|
||||
if (!in_array($dep, $validNames)) {
|
||||
$validator->errors()->add('expression', __('error.dependency_not_found', ['name' => $dep]));
|
||||
}
|
||||
}
|
||||
|
||||
// 순환 의존성 검증
|
||||
$this->validateCircularDependency($validator, $dependencies);
|
||||
}
|
||||
|
||||
/**
|
||||
* 순환 의존성 검증
|
||||
*/
|
||||
private function validateCircularDependency($validator, array $dependencies): void
|
||||
{
|
||||
if (!$this->input('model_id') || !$this->input('name')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$allFormulasQuery = ModelFormula::where('model_id', $this->input('model_id'))
|
||||
->active();
|
||||
|
||||
if ($this->route('id')) {
|
||||
$allFormulasQuery->where('id', '!=', $this->route('id'));
|
||||
}
|
||||
|
||||
$allFormulas = $allFormulasQuery->get();
|
||||
|
||||
// 현재 공식을 임시로 추가
|
||||
$tempFormula = new ModelFormula([
|
||||
'name' => $this->input('name'),
|
||||
'dependencies' => $dependencies,
|
||||
]);
|
||||
$allFormulas->push($tempFormula);
|
||||
|
||||
if ($this->hasCircularDependency($tempFormula, $allFormulas->toArray())) {
|
||||
$validator->errors()->add('expression', __('error.circular_dependency_detected'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 순환 의존성 검사
|
||||
*/
|
||||
private function hasCircularDependency(ModelFormula $formula, array $allFormulas, array $visited = []): bool
|
||||
{
|
||||
if (in_array($formula->name, $visited)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$visited[] = $formula->name;
|
||||
|
||||
foreach ($formula->dependencies ?? [] as $dep) {
|
||||
foreach ($allFormulas as $depFormula) {
|
||||
if ($depFormula->name === $dep) {
|
||||
if ($this->hasCircularDependency($depFormula, $allFormulas, $visited)) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Shared\Models\Products\ModelParameter;
|
||||
|
||||
class ModelParameterRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = [
|
||||
'model_id' => 'required|integer|exists:models,id',
|
||||
'name' => 'required|string|max:50|regex:/^[a-zA-Z][a-zA-Z0-9_]*$/',
|
||||
'label' => 'required|string|max:100',
|
||||
'type' => 'required|string|in:' . implode(',', ModelParameter::TYPES),
|
||||
'unit' => 'nullable|string|max:20',
|
||||
'validation_rules' => 'nullable|array',
|
||||
'options' => 'nullable|array',
|
||||
'default_value' => 'nullable',
|
||||
'order' => 'nullable|integer|min:1',
|
||||
'description' => 'nullable|string|max:500',
|
||||
'is_required' => 'boolean',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
|
||||
// 타입별 세부 검증
|
||||
if ($this->input('type') === ModelParameter::TYPE_NUMBER) {
|
||||
$rules['validation_rules.min'] = 'nullable|numeric';
|
||||
$rules['validation_rules.max'] = 'nullable|numeric|gte:validation_rules.min';
|
||||
$rules['default_value'] = 'nullable|numeric';
|
||||
}
|
||||
|
||||
if ($this->input('type') === ModelParameter::TYPE_SELECT) {
|
||||
$rules['options'] = 'required|array|min:1';
|
||||
$rules['options.*'] = 'required|string|max:100';
|
||||
$rules['default_value'] = 'nullable|string|in_array:options';
|
||||
}
|
||||
|
||||
if ($this->input('type') === ModelParameter::TYPE_BOOLEAN) {
|
||||
$rules['default_value'] = 'nullable|boolean';
|
||||
}
|
||||
|
||||
if ($this->input('type') === ModelParameter::TYPE_TEXT) {
|
||||
$rules['validation_rules.max_length'] = 'nullable|integer|min:1|max:1000';
|
||||
$rules['validation_rules.pattern'] = 'nullable|string|max:200';
|
||||
$rules['default_value'] = 'nullable|string';
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'name.regex' => __('error.parameter_name_format'),
|
||||
'validation_rules.max.gte' => __('error.max_must_be_greater_than_min'),
|
||||
'options.required' => __('error.select_type_requires_options'),
|
||||
'options.min' => __('error.select_type_requires_options'),
|
||||
'default_value.in_array' => __('error.default_value_not_in_options'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*/
|
||||
public function withValidator($validator): void
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
// 추가 검증 로직
|
||||
$this->validateParameterNameUnique($validator);
|
||||
$this->validateValidationRules($validator);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 매개변수명 중복 검증
|
||||
*/
|
||||
private function validateParameterNameUnique($validator): void
|
||||
{
|
||||
if (!$this->input('model_id') || !$this->input('name')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$query = ModelParameter::where('model_id', $this->input('model_id'))
|
||||
->where('name', $this->input('name'));
|
||||
|
||||
// 수정 시 자기 자신 제외
|
||||
if ($this->route('id')) {
|
||||
$query->where('id', '!=', $this->route('id'));
|
||||
}
|
||||
|
||||
if ($query->exists()) {
|
||||
$validator->errors()->add('name', __('error.parameter_name_duplicate'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 검증 규칙 유효성 검증
|
||||
*/
|
||||
private function validateValidationRules($validator): void
|
||||
{
|
||||
$type = $this->input('type');
|
||||
$validationRules = $this->input('validation_rules', []);
|
||||
|
||||
if ($type === ModelParameter::TYPE_TEXT && isset($validationRules['pattern'])) {
|
||||
// 정규식 패턴 검증
|
||||
if (@preg_match($validationRules['pattern'], '') === false) {
|
||||
$validator->errors()->add('validation_rules.pattern', __('error.invalid_regex_pattern'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ProductFromModelRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'model_id' => 'required|integer|exists:models,id',
|
||||
'parameters' => 'required|array',
|
||||
'parameters.*' => 'required',
|
||||
'product_data' => 'nullable|array',
|
||||
'product_data.name' => 'nullable|string|max:200',
|
||||
'product_data.code' => 'nullable|string|max:100|unique:products,code',
|
||||
'product_data.description' => 'nullable|string|max:1000',
|
||||
'product_data.category_id' => 'nullable|integer|exists:categories,id',
|
||||
'product_data.memo' => 'nullable|string|max:500',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'model_id.required' => __('error.model_id_required'),
|
||||
'model_id.exists' => __('error.model_not_found'),
|
||||
'parameters.required' => __('error.parameters_required'),
|
||||
'parameters.array' => __('error.parameters_must_be_array'),
|
||||
'product_data.code.unique' => __('error.product_code_duplicate'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure the validator instance.
|
||||
*/
|
||||
public function withValidator($validator): void
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
$this->validateParameters($validator);
|
||||
$this->validateProductData($validator);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 매개변수 검증
|
||||
*/
|
||||
private function validateParameters($validator): void
|
||||
{
|
||||
if (!$this->input('model_id') || !$this->input('parameters')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$parameterService = new \App\Services\ModelParameterService();
|
||||
$errors = $parameterService->validateParameterValues(
|
||||
$this->input('model_id'),
|
||||
$this->input('parameters')
|
||||
);
|
||||
|
||||
if (!empty($errors)) {
|
||||
foreach ($errors as $paramName => $paramErrors) {
|
||||
foreach ($paramErrors as $error) {
|
||||
$validator->errors()->add("parameters.{$paramName}", $error);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$validator->errors()->add('parameters', __('error.parameter_validation_failed'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 제품 데이터 검증
|
||||
*/
|
||||
private function validateProductData($validator): void
|
||||
{
|
||||
$productData = $this->input('product_data', []);
|
||||
|
||||
// 제품 코드 중복 검증 (수정 시 제외)
|
||||
if (!empty($productData['code'])) {
|
||||
$query = \Shared\Models\Products\Product::where('code', $productData['code']);
|
||||
|
||||
if ($this->route('id')) {
|
||||
$query->where('id', '!=', $this->route('id'));
|
||||
}
|
||||
|
||||
if ($query->exists()) {
|
||||
$validator->errors()->add('product_data.code', __('error.product_code_duplicate'));
|
||||
}
|
||||
}
|
||||
|
||||
// 카테고리 존재 확인
|
||||
if (!empty($productData['category_id'])) {
|
||||
$categoryExists = \Shared\Models\Products\Category::where('id', $productData['category_id'])
|
||||
->where('is_active', true)
|
||||
->exists();
|
||||
|
||||
if (!$categoryExists) {
|
||||
$validator->errors()->add('product_data.category_id', __('error.category_not_found'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
// JSON 문자열인 경우 배열로 변환
|
||||
if ($this->has('parameters') && is_string($this->input('parameters'))) {
|
||||
$this->merge([
|
||||
'parameters' => json_decode($this->input('parameters'), true) ?? []
|
||||
]);
|
||||
}
|
||||
|
||||
if ($this->has('product_data') && is_string($this->input('product_data'))) {
|
||||
$this->merge([
|
||||
'product_data' => json_decode($this->input('product_data'), true) ?? []
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user