feat: Price, PriceRevision 모델 생성 및 서비스 정리
- Price 모델 생성 (prices 테이블) - PriceRevision 모델 생성 (price_revisions 테이블) - 레거시 Pricing/PricingService 삭제 (PriceHistory 사용) - pricing 에러/메시지 추가
This commit is contained in:
221
app/Models/Products/Price.php
Normal file
221
app/Models/Products/Price.php
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\Products;
|
||||||
|
|
||||||
|
use App\Models\Orders\ClientGroup;
|
||||||
|
use App\Traits\BelongsToTenant;
|
||||||
|
use App\Traits\ModelTrait;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
|
class Price extends Model
|
||||||
|
{
|
||||||
|
use BelongsToTenant, ModelTrait, SoftDeletes;
|
||||||
|
|
||||||
|
protected $table = 'prices';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'tenant_id',
|
||||||
|
'item_type_code',
|
||||||
|
'item_id',
|
||||||
|
'client_group_id',
|
||||||
|
'purchase_price',
|
||||||
|
'processing_cost',
|
||||||
|
'loss_rate',
|
||||||
|
'margin_rate',
|
||||||
|
'sales_price',
|
||||||
|
'rounding_rule',
|
||||||
|
'rounding_unit',
|
||||||
|
'supplier',
|
||||||
|
'effective_from',
|
||||||
|
'effective_to',
|
||||||
|
'note',
|
||||||
|
'status',
|
||||||
|
'is_final',
|
||||||
|
'finalized_at',
|
||||||
|
'finalized_by',
|
||||||
|
'created_by',
|
||||||
|
'updated_by',
|
||||||
|
'deleted_by',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'item_id' => 'integer',
|
||||||
|
'client_group_id' => 'integer',
|
||||||
|
'purchase_price' => 'decimal:4',
|
||||||
|
'processing_cost' => 'decimal:4',
|
||||||
|
'loss_rate' => 'decimal:2',
|
||||||
|
'margin_rate' => 'decimal:2',
|
||||||
|
'sales_price' => 'decimal:4',
|
||||||
|
'rounding_unit' => 'integer',
|
||||||
|
'effective_from' => 'date',
|
||||||
|
'effective_to' => 'date',
|
||||||
|
'is_final' => 'boolean',
|
||||||
|
'finalized_at' => 'datetime',
|
||||||
|
'finalized_by' => 'integer',
|
||||||
|
'created_by' => 'integer',
|
||||||
|
'updated_by' => 'integer',
|
||||||
|
'deleted_by' => 'integer',
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
// Relations
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 고객 그룹 관계
|
||||||
|
*/
|
||||||
|
public function clientGroup(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(ClientGroup::class, 'client_group_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 리비전 이력
|
||||||
|
*/
|
||||||
|
public function revisions(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(PriceRevision::class, 'price_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
// Scopes
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 특정 품목 필터
|
||||||
|
*/
|
||||||
|
public function scopeForItem($query, string $itemType, int $itemId)
|
||||||
|
{
|
||||||
|
return $query->where('item_type_code', $itemType)
|
||||||
|
->where('item_id', $itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 특정 고객그룹 필터
|
||||||
|
*/
|
||||||
|
public function scopeForClientGroup($query, int $clientGroupId)
|
||||||
|
{
|
||||||
|
return $query->where('client_group_id', $clientGroupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 특정 일자에 유효한 단가 필터
|
||||||
|
*/
|
||||||
|
public function scopeValidAt($query, string $date)
|
||||||
|
{
|
||||||
|
return $query->where('effective_from', '<=', $date)
|
||||||
|
->where(function ($q) use ($date) {
|
||||||
|
$q->whereNull('effective_to')
|
||||||
|
->orWhere('effective_to', '>=', $date);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 활성 상태 단가 필터
|
||||||
|
*/
|
||||||
|
public function scopeActive($query)
|
||||||
|
{
|
||||||
|
return $query->whereIn('status', ['active', 'finalized']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
// Business Logic
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 수정 가능 여부 확인
|
||||||
|
*/
|
||||||
|
public function canEdit(): bool
|
||||||
|
{
|
||||||
|
return ! $this->is_final;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 확정 가능 여부 확인
|
||||||
|
*/
|
||||||
|
public function canFinalize(): bool
|
||||||
|
{
|
||||||
|
if ($this->is_final) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 필수 필드 확인
|
||||||
|
if (empty($this->sales_price) && empty($this->purchase_price)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 판매단가 자동 계산
|
||||||
|
* 공식: (매입단가 + 가공비) * (1 + LOSS율) * (1 + 마진율)
|
||||||
|
*/
|
||||||
|
public function calculateSalesPrice(): ?float
|
||||||
|
{
|
||||||
|
$purchasePrice = (float) ($this->purchase_price ?? 0);
|
||||||
|
$processingCost = (float) ($this->processing_cost ?? 0);
|
||||||
|
$lossRate = (float) ($this->loss_rate ?? 0) / 100;
|
||||||
|
$marginRate = (float) ($this->margin_rate ?? 0) / 100;
|
||||||
|
|
||||||
|
if ($purchasePrice <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$baseCost = $purchasePrice + $processingCost;
|
||||||
|
$withLoss = $baseCost * (1 + $lossRate);
|
||||||
|
$withMargin = $withLoss * (1 + $marginRate);
|
||||||
|
|
||||||
|
// 반올림 적용
|
||||||
|
return $this->applyRounding($withMargin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 반올림 규칙 적용
|
||||||
|
*/
|
||||||
|
private function applyRounding(float $value): float
|
||||||
|
{
|
||||||
|
$unit = $this->rounding_unit ?? 1;
|
||||||
|
$rule = $this->rounding_rule ?? 'round';
|
||||||
|
|
||||||
|
if ($unit <= 0) {
|
||||||
|
$unit = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = match ($rule) {
|
||||||
|
'ceil' => ceil($value / $unit) * $unit,
|
||||||
|
'floor' => floor($value / $unit) * $unit,
|
||||||
|
default => round($value / $unit) * $unit,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (float) $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 스냅샷 데이터 생성 (리비전용)
|
||||||
|
*/
|
||||||
|
public function toSnapshot(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'item_type_code' => $this->item_type_code,
|
||||||
|
'item_id' => $this->item_id,
|
||||||
|
'client_group_id' => $this->client_group_id,
|
||||||
|
'purchase_price' => $this->purchase_price,
|
||||||
|
'processing_cost' => $this->processing_cost,
|
||||||
|
'loss_rate' => $this->loss_rate,
|
||||||
|
'margin_rate' => $this->margin_rate,
|
||||||
|
'sales_price' => $this->sales_price,
|
||||||
|
'rounding_rule' => $this->rounding_rule,
|
||||||
|
'rounding_unit' => $this->rounding_unit,
|
||||||
|
'supplier' => $this->supplier,
|
||||||
|
'effective_from' => $this->effective_from?->toDateString(),
|
||||||
|
'effective_to' => $this->effective_to?->toDateString(),
|
||||||
|
'note' => $this->note,
|
||||||
|
'status' => $this->status,
|
||||||
|
'is_final' => $this->is_final,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
60
app/Models/Products/PriceRevision.php
Normal file
60
app/Models/Products/PriceRevision.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\Products;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Traits\BelongsToTenant;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
class PriceRevision extends Model
|
||||||
|
{
|
||||||
|
use BelongsToTenant;
|
||||||
|
|
||||||
|
protected $table = 'price_revisions';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* created_at만 사용 (updated_at 없음)
|
||||||
|
*/
|
||||||
|
public const UPDATED_AT = null;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'tenant_id',
|
||||||
|
'price_id',
|
||||||
|
'revision_number',
|
||||||
|
'changed_at',
|
||||||
|
'changed_by',
|
||||||
|
'change_reason',
|
||||||
|
'before_snapshot',
|
||||||
|
'after_snapshot',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'price_id' => 'integer',
|
||||||
|
'revision_number' => 'integer',
|
||||||
|
'changed_at' => 'datetime',
|
||||||
|
'changed_by' => 'integer',
|
||||||
|
'before_snapshot' => 'array',
|
||||||
|
'after_snapshot' => 'array',
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
// Relations
|
||||||
|
// ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 단가 관계
|
||||||
|
*/
|
||||||
|
public function price(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Price::class, 'price_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 변경자 관계
|
||||||
|
*/
|
||||||
|
public function changedByUser(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class, 'changed_by');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,285 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Services\Pricing;
|
|
||||||
|
|
||||||
use App\Models\Orders\Client;
|
|
||||||
use App\Models\Products\PriceHistory;
|
|
||||||
use App\Services\Service;
|
|
||||||
use Carbon\Carbon;
|
|
||||||
|
|
||||||
class PricingService extends Service
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* 특정 항목(제품/자재)의 단가를 조회
|
|
||||||
*
|
|
||||||
* @param string $itemType 'PRODUCT' | 'MATERIAL'
|
|
||||||
* @param int $itemId 제품/자재 ID
|
|
||||||
* @param int|null $clientId 고객 ID (NULL이면 기본 가격)
|
|
||||||
* @param string|null $date 기준일 (NULL이면 오늘)
|
|
||||||
* @return array ['price' => float|null, 'price_history_id' => int|null, 'client_group_id' => int|null, 'warning' => string|null]
|
|
||||||
*/
|
|
||||||
public function getItemPrice(string $itemType, int $itemId, ?int $clientId = null, ?string $date = null): array
|
|
||||||
{
|
|
||||||
$date = $date ?? Carbon::today()->format('Y-m-d');
|
|
||||||
$clientGroupId = null;
|
|
||||||
|
|
||||||
// 1. 고객의 그룹 ID 확인
|
|
||||||
if ($clientId) {
|
|
||||||
$client = Client::where('tenant_id', $this->tenantId())
|
|
||||||
->where('id', $clientId)
|
|
||||||
->first();
|
|
||||||
|
|
||||||
if ($client) {
|
|
||||||
$clientGroupId = $client->client_group_id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 가격 조회 (우선순위대로)
|
|
||||||
$priceHistory = null;
|
|
||||||
|
|
||||||
// 1순위: 고객 그룹별 매출단가
|
|
||||||
if ($clientGroupId) {
|
|
||||||
$priceHistory = $this->findPrice($itemType, $itemId, $clientGroupId, $date);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2순위: 기본 매출단가 (client_group_id = NULL)
|
|
||||||
if (! $priceHistory) {
|
|
||||||
$priceHistory = $this->findPrice($itemType, $itemId, null, $date);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3순위: NULL (경고)
|
|
||||||
if (! $priceHistory) {
|
|
||||||
return [
|
|
||||||
'price' => null,
|
|
||||||
'price_history_id' => null,
|
|
||||||
'client_group_id' => null,
|
|
||||||
'warning' => __('error.price_not_found', [
|
|
||||||
'item_type' => $itemType,
|
|
||||||
'item_id' => $itemId,
|
|
||||||
'date' => $date,
|
|
||||||
]),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'price' => (float) $priceHistory->price,
|
|
||||||
'price_history_id' => $priceHistory->id,
|
|
||||||
'client_group_id' => $priceHistory->client_group_id,
|
|
||||||
'warning' => null,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 특정 항목의 가격 타입별 단가 조회
|
|
||||||
*
|
|
||||||
* @param string $itemType 'PRODUCT' | 'MATERIAL'
|
|
||||||
* @param int $itemId 제품/자재 ID
|
|
||||||
* @param string $priceType 'SALE' | 'PURCHASE'
|
|
||||||
* @param int|null $clientId 고객 ID (NULL이면 기본 가격)
|
|
||||||
* @param string|null $date 기준일 (NULL이면 오늘)
|
|
||||||
* @return array ['price' => float|null, 'price_history_id' => int|null, 'client_group_id' => int|null, 'warning' => string|null]
|
|
||||||
*/
|
|
||||||
public function getPriceByType(
|
|
||||||
string $itemType,
|
|
||||||
int $itemId,
|
|
||||||
string $priceType,
|
|
||||||
?int $clientId = null,
|
|
||||||
?string $date = null
|
|
||||||
): array {
|
|
||||||
$date = $date ?? Carbon::today()->format('Y-m-d');
|
|
||||||
$clientGroupId = null;
|
|
||||||
|
|
||||||
// 1. 고객의 그룹 ID 확인
|
|
||||||
if ($clientId) {
|
|
||||||
$client = Client::where('tenant_id', $this->tenantId())
|
|
||||||
->where('id', $clientId)
|
|
||||||
->first();
|
|
||||||
|
|
||||||
if ($client) {
|
|
||||||
$clientGroupId = $client->client_group_id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 가격 조회 (우선순위대로)
|
|
||||||
$priceHistory = null;
|
|
||||||
|
|
||||||
// 1순위: 고객 그룹별 가격
|
|
||||||
if ($clientGroupId) {
|
|
||||||
$priceHistory = $this->findPriceByType($itemType, $itemId, $priceType, $clientGroupId, $date);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2순위: 기본 가격 (client_group_id = NULL)
|
|
||||||
if (! $priceHistory) {
|
|
||||||
$priceHistory = $this->findPriceByType($itemType, $itemId, $priceType, null, $date);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3순위: NULL (경고)
|
|
||||||
if (! $priceHistory) {
|
|
||||||
return [
|
|
||||||
'price' => null,
|
|
||||||
'price_history_id' => null,
|
|
||||||
'client_group_id' => null,
|
|
||||||
'warning' => __('error.price_not_found', [
|
|
||||||
'item_type' => $itemType,
|
|
||||||
'item_id' => $itemId,
|
|
||||||
'price_type' => $priceType,
|
|
||||||
'date' => $date,
|
|
||||||
]),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'price' => (float) $priceHistory->price,
|
|
||||||
'price_history_id' => $priceHistory->id,
|
|
||||||
'client_group_id' => $priceHistory->client_group_id,
|
|
||||||
'warning' => null,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 가격 이력에서 유효한 가격 조회 (SALE 타입 기본)
|
|
||||||
*/
|
|
||||||
private function findPrice(string $itemType, int $itemId, ?int $clientGroupId, string $date): ?PriceHistory
|
|
||||||
{
|
|
||||||
return $this->findPriceByType($itemType, $itemId, 'SALE', $clientGroupId, $date);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 가격 이력에서 유효한 가격 조회 (가격 타입 지정)
|
|
||||||
*
|
|
||||||
* @param string $priceType 'SALE' | 'PURCHASE'
|
|
||||||
*/
|
|
||||||
private function findPriceByType(
|
|
||||||
string $itemType,
|
|
||||||
int $itemId,
|
|
||||||
string $priceType,
|
|
||||||
?int $clientGroupId,
|
|
||||||
string $date
|
|
||||||
): ?PriceHistory {
|
|
||||||
$query = PriceHistory::where('tenant_id', $this->tenantId())
|
|
||||||
->forItem($itemType, $itemId)
|
|
||||||
->forClientGroup($clientGroupId);
|
|
||||||
|
|
||||||
// 가격 타입에 따라 스코프 적용
|
|
||||||
if ($priceType === 'PURCHASE') {
|
|
||||||
$query->purchasePrice();
|
|
||||||
} else {
|
|
||||||
$query->salePrice();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $query->validAt($date)
|
|
||||||
->orderBy('started_at', 'desc')
|
|
||||||
->first();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 여러 항목의 단가를 일괄 조회
|
|
||||||
*
|
|
||||||
* @param array $items [['item_type' => 'PRODUCT', 'item_id' => 1], ...]
|
|
||||||
* @return array ['prices' => [...], 'warnings' => [...]]
|
|
||||||
*/
|
|
||||||
public function getBulkItemPrices(array $items, ?int $clientId = null, ?string $date = null): array
|
|
||||||
{
|
|
||||||
$prices = [];
|
|
||||||
$warnings = [];
|
|
||||||
|
|
||||||
foreach ($items as $item) {
|
|
||||||
$result = $this->getItemPrice(
|
|
||||||
$item['item_type'],
|
|
||||||
$item['item_id'],
|
|
||||||
$clientId,
|
|
||||||
$date
|
|
||||||
);
|
|
||||||
|
|
||||||
$prices[] = array_merge($item, [
|
|
||||||
'price' => $result['price'],
|
|
||||||
'price_history_id' => $result['price_history_id'],
|
|
||||||
'client_group_id' => $result['client_group_id'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
if ($result['warning']) {
|
|
||||||
$warnings[] = $result['warning'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'prices' => $prices,
|
|
||||||
'warnings' => $warnings,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 가격 등록/수정
|
|
||||||
*/
|
|
||||||
public function upsertPrice(array $data): PriceHistory
|
|
||||||
{
|
|
||||||
$data['tenant_id'] = $this->tenantId();
|
|
||||||
$data['created_by'] = $this->apiUserId();
|
|
||||||
$data['updated_by'] = $this->apiUserId();
|
|
||||||
|
|
||||||
// 중복 확인: 동일 조건(item, client_group, date 범위)의 가격이 이미 있는지
|
|
||||||
$existing = PriceHistory::where('tenant_id', $data['tenant_id'])
|
|
||||||
->where('item_type_code', $data['item_type_code'])
|
|
||||||
->where('item_id', $data['item_id'])
|
|
||||||
->where('price_type_code', $data['price_type_code'])
|
|
||||||
->where('client_group_id', $data['client_group_id'] ?? null)
|
|
||||||
->where('started_at', $data['started_at'])
|
|
||||||
->first();
|
|
||||||
|
|
||||||
if ($existing) {
|
|
||||||
$existing->update($data);
|
|
||||||
|
|
||||||
return $existing->fresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
return PriceHistory::create($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 가격 이력 조회 (페이지네이션)
|
|
||||||
*
|
|
||||||
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
|
|
||||||
*/
|
|
||||||
public function listPrices(array $filters = [], int $perPage = 15)
|
|
||||||
{
|
|
||||||
$query = PriceHistory::where('tenant_id', $this->tenantId());
|
|
||||||
|
|
||||||
if (isset($filters['item_type_code'])) {
|
|
||||||
$query->where('item_type_code', $filters['item_type_code']);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($filters['item_id'])) {
|
|
||||||
$query->where('item_id', $filters['item_id']);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($filters['price_type_code'])) {
|
|
||||||
$query->where('price_type_code', $filters['price_type_code']);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($filters['client_group_id'])) {
|
|
||||||
$query->where('client_group_id', $filters['client_group_id']);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($filters['date'])) {
|
|
||||||
$query->validAt($filters['date']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $query->orderBy('started_at', 'desc')
|
|
||||||
->orderBy('created_at', 'desc')
|
|
||||||
->paginate($perPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 가격 삭제 (Soft Delete)
|
|
||||||
*/
|
|
||||||
public function deletePrice(int $id): bool
|
|
||||||
{
|
|
||||||
$price = PriceHistory::where('tenant_id', $this->tenantId())
|
|
||||||
->findOrFail($id);
|
|
||||||
|
|
||||||
$price->deleted_by = $this->apiUserId();
|
|
||||||
$price->save();
|
|
||||||
|
|
||||||
return $price->delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -131,6 +131,13 @@
|
|||||||
'self_reference_bom' => 'BOM에 자기 자신을 포함할 수 없습니다.',
|
'self_reference_bom' => 'BOM에 자기 자신을 포함할 수 없습니다.',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// 단가 관리 관련
|
||||||
|
'pricing' => [
|
||||||
|
'finalized_cannot_edit' => '확정된 단가는 수정할 수 없습니다.',
|
||||||
|
'finalized_cannot_delete' => '확정된 단가는 삭제할 수 없습니다.',
|
||||||
|
'cannot_finalize' => '확정할 수 없는 상태입니다.',
|
||||||
|
],
|
||||||
|
|
||||||
// 잠금 관련
|
// 잠금 관련
|
||||||
'relationship_locked' => '잠금된 연결은 해제할 수 없습니다.',
|
'relationship_locked' => '잠금된 연결은 해제할 수 없습니다.',
|
||||||
'has_locked_relationships' => '잠금된 연결이 포함되어 있어 처리할 수 없습니다.',
|
'has_locked_relationships' => '잠금된 연결이 포함되어 있어 처리할 수 없습니다.',
|
||||||
|
|||||||
@@ -122,6 +122,14 @@
|
|||||||
'deleted' => '자재가 삭제되었습니다.',
|
'deleted' => '자재가 삭제되었습니다.',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// 단가 관리
|
||||||
|
'pricing' => [
|
||||||
|
'created' => '단가가 등록되었습니다.',
|
||||||
|
'updated' => '단가가 수정되었습니다.',
|
||||||
|
'deleted' => '단가가 삭제되었습니다.',
|
||||||
|
'finalized' => '단가가 확정되었습니다.',
|
||||||
|
],
|
||||||
|
|
||||||
// 거래처 관리
|
// 거래처 관리
|
||||||
'client' => [
|
'client' => [
|
||||||
'fetched' => '거래처를 조회했습니다.',
|
'fetched' => '거래처를 조회했습니다.',
|
||||||
|
|||||||
Reference in New Issue
Block a user