feat: [order] 재고생산관리(STOCK) 타입 추가
- Order 모델에 TYPE_STOCK = 'STOCK' 상수 추가
- StoreOrderRequest/UpdateOrderRequest에 STOCK 타입 validation 추가
- options에 production_reason, target_stock_qty 필드 추가
- 재고생산 채번: STK{YYYYMMDD}{NNNN} 형식
- stats()에 order_type 필터 파라미터 추가
- STOCK 타입 확정 시 매출 자동 생성 스킵
This commit is contained in:
@@ -30,10 +30,10 @@ public function index(Request $request)
|
||||
/**
|
||||
* 통계 조회
|
||||
*/
|
||||
public function stats()
|
||||
public function stats(Request $request)
|
||||
{
|
||||
return ApiResponse::handle(function () {
|
||||
return $this->service->stats();
|
||||
return ApiResponse::handle(function () use ($request) {
|
||||
return $this->service->stats($request->input('order_type'));
|
||||
}, __('message.order.fetched'));
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ public function rules(): array
|
||||
return [
|
||||
// 기본 정보
|
||||
'quote_id' => 'nullable|integer|exists:quotes,id',
|
||||
'order_type_code' => ['nullable', Rule::in([Order::TYPE_ORDER, Order::TYPE_PURCHASE])],
|
||||
'order_type_code' => ['nullable', Rule::in([Order::TYPE_ORDER, Order::TYPE_PURCHASE, Order::TYPE_STOCK])],
|
||||
'status_code' => ['nullable', Rule::in([
|
||||
Order::STATUS_DRAFT,
|
||||
Order::STATUS_CONFIRMED,
|
||||
@@ -55,6 +55,8 @@ public function rules(): array
|
||||
'options.shipping_address' => 'nullable|string|max:500',
|
||||
'options.shipping_address_detail' => 'nullable|string|max:500',
|
||||
'options.manager_name' => 'nullable|string|max:100',
|
||||
'options.production_reason' => 'nullable|string|max:500',
|
||||
'options.target_stock_qty' => 'nullable|numeric|min:0',
|
||||
|
||||
// 품목 배열
|
||||
'items' => 'nullable|array',
|
||||
|
||||
@@ -17,7 +17,7 @@ public function rules(): array
|
||||
{
|
||||
return [
|
||||
// 기본 정보 (order_no는 수정 불가)
|
||||
'order_type_code' => ['nullable', Rule::in([Order::TYPE_ORDER, Order::TYPE_PURCHASE])],
|
||||
'order_type_code' => ['nullable', Rule::in([Order::TYPE_ORDER, Order::TYPE_PURCHASE, Order::TYPE_STOCK])],
|
||||
'category_code' => 'nullable|string|max:50',
|
||||
|
||||
// 거래처 정보
|
||||
@@ -49,6 +49,8 @@ public function rules(): array
|
||||
'options.shipping_address' => 'nullable|string|max:500',
|
||||
'options.shipping_address_detail' => 'nullable|string|max:500',
|
||||
'options.manager_name' => 'nullable|string|max:100',
|
||||
'options.production_reason' => 'nullable|string|max:500',
|
||||
'options.target_stock_qty' => 'nullable|numeric|min:0',
|
||||
|
||||
// 품목 배열 (전체 교체)
|
||||
'items' => 'nullable|array',
|
||||
|
||||
@@ -78,6 +78,8 @@ class Order extends Model
|
||||
|
||||
public const TYPE_PURCHASE = 'PURCHASE'; // 발주
|
||||
|
||||
public const TYPE_STOCK = 'STOCK'; // 재고생산
|
||||
|
||||
// 매출 인식 시점
|
||||
public const SALES_ON_ORDER_CONFIRM = 'on_order_confirm'; // 수주확정 시
|
||||
|
||||
|
||||
@@ -109,17 +109,22 @@ public function index(array $params)
|
||||
/**
|
||||
* 통계 조회
|
||||
*/
|
||||
public function stats(): array
|
||||
public function stats(?string $orderType = null): array
|
||||
{
|
||||
$tenantId = $this->tenantId();
|
||||
|
||||
$counts = Order::where('tenant_id', $tenantId)
|
||||
$baseQuery = Order::where('tenant_id', $tenantId);
|
||||
if ($orderType !== null) {
|
||||
$baseQuery->where('order_type_code', $orderType);
|
||||
}
|
||||
|
||||
$counts = (clone $baseQuery)
|
||||
->select('status_code', DB::raw('count(*) as count'))
|
||||
->groupBy('status_code')
|
||||
->pluck('count', 'status_code')
|
||||
->toArray();
|
||||
|
||||
$amounts = Order::where('tenant_id', $tenantId)
|
||||
$amounts = (clone $baseQuery)
|
||||
->select('status_code', DB::raw('sum(total_amount) as total'))
|
||||
->groupBy('status_code')
|
||||
->pluck('total', 'status_code')
|
||||
@@ -162,10 +167,13 @@ public function store(array $data)
|
||||
$userId = $this->apiUserId();
|
||||
|
||||
return DB::transaction(function () use ($data, $tenantId, $userId) {
|
||||
// 수주번호 자동 생성
|
||||
// 수주번호 자동 생성 (재고생산은 STK 접두사)
|
||||
$pairCode = $data['pair_code'] ?? null;
|
||||
unset($data['pair_code']);
|
||||
$data['order_no'] = $this->generateOrderNo($tenantId, $pairCode);
|
||||
$isStock = ($data['order_type_code'] ?? null) === Order::TYPE_STOCK;
|
||||
$data['order_no'] = $isStock
|
||||
? $this->generateStockOrderNo($tenantId)
|
||||
: $this->generateOrderNo($tenantId, $pairCode);
|
||||
$data['tenant_id'] = $tenantId;
|
||||
$data['created_by'] = $userId;
|
||||
$data['updated_by'] = $userId;
|
||||
@@ -629,8 +637,8 @@ public function updateStatus(int $id, string $status)
|
||||
$createdSale = null;
|
||||
$previousStatus = $order->status_code;
|
||||
|
||||
// 수주확정 시 매출 자동 생성 (sales_recognition = on_order_confirm인 경우)
|
||||
if ($status === Order::STATUS_CONFIRMED && $order->shouldCreateSaleOnConfirm()) {
|
||||
// 수주확정 시 매출 자동 생성 (재고생산은 매출 생성 불필요)
|
||||
if ($status === Order::STATUS_CONFIRMED && $order->order_type_code !== Order::TYPE_STOCK && $order->shouldCreateSaleOnConfirm()) {
|
||||
$createdSale = $this->createSaleFromOrder($order, $userId);
|
||||
$order->sale_id = $createdSale->id;
|
||||
}
|
||||
@@ -776,6 +784,29 @@ private function generateOrderNoLegacy(int $tenantId): string
|
||||
return sprintf('%s%s%04d', $prefix, $date, $seq);
|
||||
}
|
||||
|
||||
/**
|
||||
* 재고생산 번호 생성 (STK{YYYYMMDD}{NNNN})
|
||||
*/
|
||||
private function generateStockOrderNo(int $tenantId): string
|
||||
{
|
||||
$prefix = 'STK';
|
||||
$date = now()->format('Ymd');
|
||||
|
||||
$lastNo = Order::withoutGlobalScopes()
|
||||
->where('tenant_id', $tenantId)
|
||||
->where('order_no', 'like', "{$prefix}{$date}%")
|
||||
->orderByDesc('order_no')
|
||||
->value('order_no');
|
||||
|
||||
if ($lastNo) {
|
||||
$seq = (int) substr($lastNo, -4) + 1;
|
||||
} else {
|
||||
$seq = 1;
|
||||
}
|
||||
|
||||
return sprintf('%s%s%04d', $prefix, $date, $seq);
|
||||
}
|
||||
|
||||
/**
|
||||
* 견적에서 수주 생성
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user