feat: 단가 관리 API 구현 및 Flow Tester 호환성 개선
- Price, PriceRevision 모델 추가 (PriceHistory 대체) - PricingService: CRUD, 원가 조회, 확정 기능 - PricingController: statusCode 파라미터로 201 반환 지원 - NotFoundHttpException(404) 적용 (존재하지 않는 리소스) - FormRequest 분리 (Store, Update, Index, Cost, ByItems) - Swagger 문서 업데이트 - ApiResponse::handle()에 statusCode 옵션 추가 - prices/price_revisions 마이그레이션 및 데이터 이관
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('prices', function (Blueprint $table) {
|
||||
$table->id()->comment('ID');
|
||||
$table->foreignId('tenant_id')->comment('테넌트 ID');
|
||||
|
||||
// 품목 연결
|
||||
$table->string('item_type_code', 20)->comment('품목유형 (PRODUCT/MATERIAL)');
|
||||
$table->unsignedBigInteger('item_id')->comment('품목 ID');
|
||||
$table->unsignedBigInteger('client_group_id')->nullable()->comment('고객그룹 ID (NULL=기본가)');
|
||||
|
||||
// 원가 정보
|
||||
$table->decimal('purchase_price', 15, 4)->nullable()->comment('매입단가 (표준원가)');
|
||||
$table->decimal('processing_cost', 15, 4)->nullable()->comment('가공비');
|
||||
$table->decimal('loss_rate', 5, 2)->nullable()->comment('LOSS율 (%)');
|
||||
|
||||
// 판매가 정보
|
||||
$table->decimal('margin_rate', 5, 2)->nullable()->comment('마진율 (%)');
|
||||
$table->decimal('sales_price', 15, 4)->nullable()->comment('판매단가');
|
||||
$table->enum('rounding_rule', ['round', 'ceil', 'floor'])->default('round')->comment('반올림 규칙');
|
||||
$table->integer('rounding_unit')->default(1)->comment('반올림 단위 (1,10,100,1000)');
|
||||
|
||||
// 메타 정보
|
||||
$table->string('supplier', 255)->nullable()->comment('공급업체');
|
||||
$table->date('effective_from')->comment('적용 시작일');
|
||||
$table->date('effective_to')->nullable()->comment('적용 종료일');
|
||||
$table->text('note')->nullable()->comment('비고');
|
||||
|
||||
// 상태 관리
|
||||
$table->enum('status', ['draft', 'active', 'inactive', 'finalized'])->default('draft')->comment('상태');
|
||||
$table->boolean('is_final')->default(false)->comment('최종 확정 여부');
|
||||
$table->dateTime('finalized_at')->nullable()->comment('확정 일시');
|
||||
$table->unsignedBigInteger('finalized_by')->nullable()->comment('확정자 ID');
|
||||
|
||||
// 감사 컬럼
|
||||
$table->foreignId('created_by')->nullable()->comment('생성자 ID');
|
||||
$table->foreignId('updated_by')->nullable()->comment('수정자 ID');
|
||||
$table->foreignId('deleted_by')->nullable()->comment('삭제자 ID');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
// 인덱스
|
||||
$table->index('tenant_id', 'idx_prices_tenant');
|
||||
$table->index(['tenant_id', 'item_type_code', 'item_id'], 'idx_prices_item');
|
||||
$table->index(['tenant_id', 'effective_from', 'effective_to'], 'idx_prices_effective');
|
||||
$table->index(['tenant_id', 'status'], 'idx_prices_status');
|
||||
$table->unique(
|
||||
['tenant_id', 'item_type_code', 'item_id', 'client_group_id', 'effective_from', 'deleted_at'],
|
||||
'idx_prices_unique'
|
||||
);
|
||||
|
||||
// Foreign Key
|
||||
$table->foreign('client_group_id')->references('id')->on('client_groups')->onDelete('set null');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('prices');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('price_revisions', function (Blueprint $table) {
|
||||
$table->id()->comment('ID');
|
||||
$table->foreignId('tenant_id')->comment('테넌트 ID');
|
||||
$table->foreignId('price_id')->comment('단가 ID');
|
||||
|
||||
// 리비전 정보
|
||||
$table->integer('revision_number')->comment('리비전 번호');
|
||||
$table->dateTime('changed_at')->comment('변경 일시');
|
||||
$table->unsignedBigInteger('changed_by')->comment('변경자 ID');
|
||||
$table->string('change_reason', 500)->nullable()->comment('변경 사유');
|
||||
|
||||
// 변경 스냅샷 (JSON)
|
||||
$table->json('before_snapshot')->nullable()->comment('변경 전 데이터');
|
||||
$table->json('after_snapshot')->comment('변경 후 데이터');
|
||||
|
||||
$table->timestamp('created_at')->useCurrent();
|
||||
|
||||
// 인덱스
|
||||
$table->index('price_id', 'idx_revisions_price');
|
||||
$table->index('tenant_id', 'idx_revisions_tenant');
|
||||
$table->unique(['price_id', 'revision_number'], 'idx_revisions_unique');
|
||||
|
||||
// Foreign Key
|
||||
$table->foreign('price_id')->references('id')->on('prices')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('price_revisions');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
* price_histories 데이터를 prices 테이블로 이관
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
// 기존 price_histories 데이터를 prices로 이관
|
||||
DB::statement("
|
||||
INSERT INTO prices (
|
||||
tenant_id,
|
||||
item_type_code,
|
||||
item_id,
|
||||
client_group_id,
|
||||
purchase_price,
|
||||
sales_price,
|
||||
effective_from,
|
||||
effective_to,
|
||||
status,
|
||||
created_by,
|
||||
updated_by,
|
||||
deleted_by,
|
||||
created_at,
|
||||
updated_at,
|
||||
deleted_at
|
||||
)
|
||||
SELECT
|
||||
tenant_id,
|
||||
item_type_code,
|
||||
item_id,
|
||||
client_group_id,
|
||||
CASE WHEN price_type_code = 'PURCHASE' THEN price ELSE NULL END as purchase_price,
|
||||
CASE WHEN price_type_code = 'SALE' THEN price ELSE NULL END as sales_price,
|
||||
started_at as effective_from,
|
||||
ended_at as effective_to,
|
||||
'active' as status,
|
||||
created_by,
|
||||
updated_by,
|
||||
deleted_by,
|
||||
created_at,
|
||||
updated_at,
|
||||
deleted_at
|
||||
FROM price_histories
|
||||
WHERE deleted_at IS NULL
|
||||
");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
// prices 테이블에서 이관된 데이터 삭제
|
||||
// (원본 price_histories는 아직 존재하므로 prices만 비움)
|
||||
DB::table('prices')->truncate();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
* price_histories 테이블 삭제 (prices로 완전 이관 완료 후)
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::dropIfExists('price_histories');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
* 롤백 시 price_histories 테이블 재생성
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::create('price_histories', function (Blueprint $table) {
|
||||
$table->id()->comment('ID');
|
||||
$table->foreignId('tenant_id')->comment('테넌트 ID');
|
||||
$table->string('item_type_code', 20)->comment('품목유형 (PRODUCT/MATERIAL)');
|
||||
$table->unsignedBigInteger('item_id')->comment('품목 ID');
|
||||
$table->string('price_type_code', 20)->comment('가격유형 (SALE/PURCHASE)');
|
||||
$table->unsignedBigInteger('client_group_id')->nullable()->comment('고객 그룹 ID');
|
||||
$table->decimal('price', 15, 4)->comment('단가');
|
||||
$table->date('started_at')->comment('적용 시작일');
|
||||
$table->date('ended_at')->nullable()->comment('적용 종료일');
|
||||
$table->foreignId('created_by')->nullable()->comment('생성자 ID');
|
||||
$table->foreignId('updated_by')->nullable()->comment('수정자 ID');
|
||||
$table->foreignId('deleted_by')->nullable()->comment('삭제자 ID');
|
||||
$table->timestamps();
|
||||
$table->softDeletes();
|
||||
|
||||
$table->index(['tenant_id', 'item_type_code', 'item_id', 'client_group_id', 'started_at'], 'idx_price_histories_main');
|
||||
$table->foreign('client_group_id')->references('id')->on('client_groups')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user