Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
52
app/Http/Controllers/Api/V1/MaterialController.php
Normal file
52
app/Http/Controllers/Api/V1/MaterialController.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Material\MaterialStoreRequest;
|
||||
use App\Http\Requests\Material\MaterialUpdateRequest;
|
||||
use App\Services\MaterialService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class MaterialController extends Controller
|
||||
{
|
||||
public function __construct(private MaterialService $service) {}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->getMaterials($request->all());
|
||||
}, __('message.material.fetched'));
|
||||
}
|
||||
|
||||
public function store(MaterialStoreRequest $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
// 동적 필드 지원을 위해 전체 입력값 전달 (Service에서 검증)
|
||||
return $this->service->setMaterial($request->all());
|
||||
}, __('message.material.created'));
|
||||
}
|
||||
|
||||
public function show(int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
return $this->service->getMaterial($id);
|
||||
}, __('message.material.fetched'));
|
||||
}
|
||||
|
||||
public function update(MaterialUpdateRequest $request, int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $id) {
|
||||
// 동적 필드 지원을 위해 전체 입력값 전달 (Service에서 검증)
|
||||
return $this->service->updateMaterial($id, $request->all());
|
||||
}, __('message.material.updated'));
|
||||
}
|
||||
|
||||
public function destroy(int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
return $this->service->destroyMaterial($id);
|
||||
}, __('message.material.deleted'));
|
||||
}
|
||||
}
|
||||
114
app/Http/Controllers/Api/V1/ProductBomItemController.php
Normal file
114
app/Http/Controllers/Api/V1/ProductBomItemController.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\ProductBomService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ProductBomItemController extends Controller
|
||||
{
|
||||
public function __construct(private ProductBomService $service) {}
|
||||
|
||||
// GET /products/{id}/bom/items
|
||||
public function index(int $id, Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id, $request) {
|
||||
return $this->service->index($id, $request->all());
|
||||
}, 'BOM 항목 목록');
|
||||
}
|
||||
|
||||
// POST /products/{id}/bom/items/bulk
|
||||
public function bulkUpsert(int $id, Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id, $request) {
|
||||
return $this->service->bulkUpsert($id, $request->input('items', []));
|
||||
}, 'BOM 일괄 업서트');
|
||||
}
|
||||
|
||||
// PATCH /products/{id}/bom/items/{item}
|
||||
public function update(int $id, int $item, Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id, $item, $request) {
|
||||
return $this->service->update($id, $item, $request->all());
|
||||
}, 'BOM 항목 수정');
|
||||
}
|
||||
|
||||
// DELETE /products/{id}/bom/items/{item}
|
||||
public function destroy(int $id, int $item)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id, $item) {
|
||||
$this->service->destroy($id, $item);
|
||||
|
||||
return 'success';
|
||||
}, 'BOM 항목 삭제');
|
||||
}
|
||||
|
||||
// POST /products/{id}/bom/items/reorder
|
||||
public function reorder(int $id, Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id, $request) {
|
||||
$this->service->reorder($id, $request->input('items', []));
|
||||
|
||||
return 'success';
|
||||
}, 'BOM 정렬 변경');
|
||||
}
|
||||
|
||||
// GET /products/{id}/bom/summary
|
||||
public function summary(int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
return $this->service->summary($id);
|
||||
}, 'BOM 요약');
|
||||
}
|
||||
|
||||
// GET /products/{id}/bom/validate
|
||||
public function validateBom(int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
return $this->service->validateBom($id);
|
||||
}, 'BOM 유효성 검사');
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/v1/products/{id}/bom
|
||||
* BOM 구성 저장 (기존 전체 삭제 후 재등록)
|
||||
*/
|
||||
public function replace(Request $request, int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request, $id) {
|
||||
// 서비스에서 트랜잭션 처리 + 예외는 글로벌 핸들러로
|
||||
return $this->service->replaceBom($id, $request->all());
|
||||
}, __('message.bom.creat'));
|
||||
}
|
||||
|
||||
/** 특정 제품 BOM에서 사용 중인 카테고리 목록 */
|
||||
public function listCategories(int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
return $this->service->listCategoriesForProduct($id);
|
||||
}, __('message.bom.fetch'));
|
||||
}
|
||||
|
||||
/** 테넌트 전역 카테고리 추천(히스토리) */
|
||||
public function suggestCategories(Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
$q = $request->query('q');
|
||||
$limit = (int) ($request->query('limit', 20));
|
||||
|
||||
return $this->service->listCategoriesForTenant($q, $limit);
|
||||
}, __('message.bom.fetch'));
|
||||
}
|
||||
|
||||
/** Bom Tree */
|
||||
public function tree(Request $request, int $id)
|
||||
{
|
||||
return ApiResponse::handle(
|
||||
function () use ($request, $id) {
|
||||
return $this->service->tree($request, $id);
|
||||
}, __('message.bom.fetch')
|
||||
);
|
||||
}
|
||||
}
|
||||
82
app/Http/Controllers/Api/V1/ProductController.php
Normal file
82
app/Http/Controllers/Api/V1/ProductController.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Helpers\ApiResponse;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Product\ProductStoreRequest;
|
||||
use App\Http\Requests\Product\ProductUpdateRequest;
|
||||
use App\Services\ProductService;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ProductController extends Controller
|
||||
{
|
||||
public function __construct(private ProductService $service) {}
|
||||
|
||||
public function getCategory(Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->getCategory($request);
|
||||
}, __('message.product.category_fetched'));
|
||||
}
|
||||
|
||||
// GET /products
|
||||
public function index(Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->index($request->all());
|
||||
}, __('message.product.fetched'));
|
||||
}
|
||||
|
||||
// POST /products
|
||||
public function store(ProductStoreRequest $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->store($request->validated());
|
||||
}, __('message.product.created'));
|
||||
}
|
||||
|
||||
// GET /products/{id}
|
||||
public function show(int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
return $this->service->show($id);
|
||||
}, __('message.product.fetched'));
|
||||
}
|
||||
|
||||
// PATCH /products/{id}
|
||||
public function update(int $id, ProductUpdateRequest $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id, $request) {
|
||||
return $this->service->update($id, $request->validated());
|
||||
}, __('message.product.updated'));
|
||||
}
|
||||
|
||||
// DELETE /products/{id}
|
||||
public function destroy(int $id)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($id) {
|
||||
$this->service->destroy($id);
|
||||
|
||||
return 'success';
|
||||
}, __('message.product.deleted'));
|
||||
}
|
||||
|
||||
// GET /products/search
|
||||
public function search(Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->search($request->all());
|
||||
}, __('message.product.searched'));
|
||||
}
|
||||
|
||||
// Note: toggle 메서드는 is_active 필드 제거로 인해 비활성화됨
|
||||
// 필요시 attributes JSON이나 별도 필드로 구현
|
||||
// POST /products/{id}/toggle
|
||||
// public function toggle(int $id)
|
||||
// {
|
||||
// return ApiResponse::handle(function () use ($id) {
|
||||
// return $this->service->toggle($id);
|
||||
// }, __('message.product.toggled'));
|
||||
// }
|
||||
}
|
||||
32
app/Http/Requests/Material/MaterialStoreRequest.php
Normal file
32
app/Http/Requests/Material/MaterialStoreRequest.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Material;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class MaterialStoreRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'category_id' => 'nullable|integer',
|
||||
'name' => 'required|string|max:100',
|
||||
'unit' => 'required|string|max:20',
|
||||
'is_inspection' => 'nullable|in:Y,N',
|
||||
'search_tag' => 'nullable|string|max:255',
|
||||
'remarks' => 'nullable|string|max:500',
|
||||
'attributes' => 'nullable|array',
|
||||
'attributes.*.label' => 'required|string|max:50',
|
||||
'attributes.*.value' => 'required|string|max:100',
|
||||
'attributes.*.unit' => 'nullable|string|max:20',
|
||||
'options' => 'nullable|array',
|
||||
'material_code' => 'nullable|string|max:30',
|
||||
'specification' => 'nullable|string|max:255',
|
||||
];
|
||||
}
|
||||
}
|
||||
32
app/Http/Requests/Material/MaterialUpdateRequest.php
Normal file
32
app/Http/Requests/Material/MaterialUpdateRequest.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Material;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class MaterialUpdateRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'category_id' => 'nullable|integer',
|
||||
'name' => 'sometimes|string|max:100',
|
||||
'unit' => 'sometimes|string|max:20',
|
||||
'is_inspection' => 'nullable|in:Y,N',
|
||||
'search_tag' => 'nullable|string|max:255',
|
||||
'remarks' => 'nullable|string|max:500',
|
||||
'attributes' => 'nullable|array',
|
||||
'attributes.*.label' => 'required|string|max:50',
|
||||
'attributes.*.value' => 'required|string|max:100',
|
||||
'attributes.*.unit' => 'nullable|string|max:20',
|
||||
'options' => 'nullable|array',
|
||||
'material_code' => 'nullable|string|max:30',
|
||||
'specification' => 'nullable|string|max:255',
|
||||
];
|
||||
}
|
||||
}
|
||||
42
app/Http/Requests/Product/ProductStoreRequest.php
Normal file
42
app/Http/Requests/Product/ProductStoreRequest.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Product;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ProductStoreRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
// 기본 필드
|
||||
'code' => 'required|string|max:30',
|
||||
'name' => 'required|string|max:100',
|
||||
'unit' => 'nullable|string|max:10',
|
||||
'category_id' => 'required|integer',
|
||||
'product_type' => 'required|string|max:30',
|
||||
'description' => 'nullable|string|max:255',
|
||||
|
||||
// 상태 플래그
|
||||
'is_sellable' => 'nullable|boolean',
|
||||
'is_purchasable' => 'nullable|boolean',
|
||||
'is_producible' => 'nullable|boolean',
|
||||
|
||||
// 하이브리드 구조: 고정 필드
|
||||
'safety_stock' => 'nullable|integer|min:0',
|
||||
'lead_time' => 'nullable|integer|min:0',
|
||||
'is_variable_size' => 'nullable|boolean',
|
||||
'product_category' => 'nullable|string|max:20',
|
||||
'part_type' => 'nullable|string|max:20',
|
||||
|
||||
// 하이브리드 구조: 동적 필드
|
||||
'attributes' => 'nullable|array',
|
||||
'attributes_archive' => 'nullable|array',
|
||||
];
|
||||
}
|
||||
}
|
||||
42
app/Http/Requests/Product/ProductUpdateRequest.php
Normal file
42
app/Http/Requests/Product/ProductUpdateRequest.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Product;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ProductUpdateRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
// 기본 필드
|
||||
'code' => 'sometimes|string|max:30',
|
||||
'name' => 'sometimes|string|max:100',
|
||||
'unit' => 'nullable|string|max:10',
|
||||
'category_id' => 'sometimes|integer',
|
||||
'product_type' => 'sometimes|string|max:30',
|
||||
'description' => 'nullable|string|max:255',
|
||||
|
||||
// 상태 플래그
|
||||
'is_sellable' => 'nullable|boolean',
|
||||
'is_purchasable' => 'nullable|boolean',
|
||||
'is_producible' => 'nullable|boolean',
|
||||
|
||||
// 하이브리드 구조: 고정 필드
|
||||
'safety_stock' => 'nullable|integer|min:0',
|
||||
'lead_time' => 'nullable|integer|min:0',
|
||||
'is_variable_size' => 'nullable|boolean',
|
||||
'product_category' => 'nullable|string|max:20',
|
||||
'part_type' => 'nullable|string|max:20',
|
||||
|
||||
// 하이브리드 구조: 동적 필드
|
||||
'attributes' => 'nullable|array',
|
||||
'attributes_archive' => 'nullable|array',
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user