dbmsv4 init...5
This commit is contained in:
parent
4a1b40c39f
commit
43dd7caf02
@ -180,34 +180,6 @@ abstract class CommonForm
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 2) 숫자/FK 필드 정규화
|
||||
* - 폼에서 미선택은 보통 ''로 들어옴 -> NULL로 변환
|
||||
* - 숫자 문자열은 int 캐스팅 (선택)
|
||||
*
|
||||
* 주의:
|
||||
* - "빈값을 0으로 취급" 같은 정책이 있다면 여기에서 조정해야 함.
|
||||
*/
|
||||
protected function normalizeNumericEmptyToNull(array $data, array $numericFields): array
|
||||
{
|
||||
foreach ($numericFields as $f) {
|
||||
if (!array_key_exists($f, $data)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($data[$f] === '') {
|
||||
$data[$f] = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_string($data[$f]) && ctype_digit($data[$f])) {
|
||||
$data[$f] = (int) $data[$f];
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 3) role.* 같은 배열 원소 규칙이 있을 때, 부모 배열 존재/타입 보정
|
||||
*/
|
||||
@ -259,38 +231,6 @@ abstract class CommonForm
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 4) 검증 rule에 따라 "numeric(특히 FK)"로 취급할 필드를 수집
|
||||
* - getFormRule()에서 permit_empty|numeric 로 정의되는 필드를 공통 처리하기 위함
|
||||
*
|
||||
* 구현 전략:
|
||||
* - formRules에서 rule 문자열에 'numeric'가 포함된 필드를 모음
|
||||
* - wildcard(role.*) 제외
|
||||
*/
|
||||
protected function collectNumericFieldsFromRules(array $formRules): array
|
||||
{
|
||||
$numericFields = [];
|
||||
|
||||
foreach ($formRules as $field => $rule) {
|
||||
$fieldName = (string) $field;
|
||||
|
||||
if (str_contains($fieldName, '.*')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// getValidationRule hook 적용 (필드명/룰이 바뀔 수 있으니)
|
||||
[$fieldName, $ruleStr] = $this->getValidationRule($fieldName, (string) $rule);
|
||||
|
||||
if (is_string($ruleStr) && str_contains($ruleStr, 'numeric')) {
|
||||
$numericFields[] = $fieldName;
|
||||
}
|
||||
}
|
||||
|
||||
// 중복 제거
|
||||
return array_values(array_unique($numericFields));
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
* Validation
|
||||
* --------------------------------------------------------------------- */
|
||||
@ -299,13 +239,8 @@ abstract class CommonForm
|
||||
* 데이터를 검증하고 유효하지 않을 경우 예외를 발생시킵니다.
|
||||
* 2025 CI4 표준: 규칙 배열 내에 label을 포함하여 한글 메시지 출력을 보장합니다.
|
||||
*/
|
||||
final public function validate(array &$formDatas): void
|
||||
public function validate_process(array &$formDatas, $isDebug = false): array
|
||||
{
|
||||
log_message('debug', '>>> CommonForm::validate CALLED: ' . static::class . ', formAction:' . $this->formAction);
|
||||
try {
|
||||
// 0) 데이터 구조 정리 (null 변환 X)
|
||||
$formDatas = $this->sanitizeFormDatas($formDatas);
|
||||
|
||||
// 1) 전체 라벨/룰
|
||||
$allFormFields = $this->getFormFields(); // ['field' => '라벨', ...]
|
||||
$allFormRules = $this->getFormRules(); // ['field' => 'rules', ...]
|
||||
@ -313,54 +248,61 @@ abstract class CommonForm
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: 지정된 Form RULE이 없습니다.");
|
||||
}
|
||||
|
||||
// 2) 액션별 "검증 대상 필드" 결정
|
||||
$formRules = $allFormRules;
|
||||
$formFields = $allFormFields;
|
||||
if ($this->formAction === 'modify') {
|
||||
// (1) formDatas에 실제로 넘어온 필드만
|
||||
$targetFields = array_keys($formDatas);
|
||||
|
||||
// (2) 내부 제어용 키 제거(프로젝트에 맞게 추가/삭제)
|
||||
$exclude = ['_method', 'csrf_test_name', 'submit', 'token', 'action'];
|
||||
$targetFields = array_values(array_diff($targetFields, $exclude));
|
||||
|
||||
// (3) wildcard(role.*) 같은 규칙이 있으면 부모 기반으로 같이 포함
|
||||
// - formDatas에 role이 있으면 role.* 규칙도 함께 검사되도록 추가
|
||||
foreach ($allFormRules as $ruleField => $_ruleStr) {
|
||||
$ruleField = (string) $ruleField;
|
||||
if (!str_contains($ruleField, '.*')) {
|
||||
continue;
|
||||
if ($isDebug) {
|
||||
log_message('debug', static::class . '->' . __FUNCTION__ . "에서 Validate targetFields Begin DEBUG");
|
||||
log_message('debug', var_export($targetFields, true));
|
||||
}
|
||||
$parent = str_replace('.*', '', $ruleField);
|
||||
if (in_array($parent, $targetFields, true)) {
|
||||
$targetFields[] = $ruleField; // e.g. 'role.*'
|
||||
// // (2) 내부 제어용 키 제거(프로젝트에 맞게 추가/삭제)
|
||||
// $exclude = ['_method', 'csrf_test_name', 'submit', 'token', 'action'];
|
||||
// $targetFields = array_values(array_diff($targetFields, $exclude));
|
||||
// // (3) wildcard(role.*) 같은 규칙이 있으면 부모 기반으로 같이 포함
|
||||
// // - formDatas에 role이 있으면 role.* 규칙도 함께 검사되도록 추가
|
||||
// foreach ($allFormRules as $ruleField => $_ruleStr) {
|
||||
// $ruleField = (string) $ruleField;
|
||||
// if (!str_contains($ruleField, '.*')) {
|
||||
// continue;
|
||||
// }
|
||||
// $parent = str_replace('.*', '', $ruleField);
|
||||
// if (in_array($parent, $targetFields, true)) {
|
||||
// $targetFields[] = $ruleField; // e.g. 'role.*'
|
||||
// }
|
||||
// }
|
||||
// // (4) 실제로 룰이 정의된 필드만 남김
|
||||
// $targetFields = array_values(array_intersect($targetFields, array_keys($allFormRules)));
|
||||
if ($isDebug) {
|
||||
log_message('debug', static::class . '->' . __FUNCTION__ . "에서 Validate targetFields End DEBUG");
|
||||
log_message('debug', var_export($targetFields, true));
|
||||
}
|
||||
}
|
||||
|
||||
// (4) 실제로 룰이 정의된 필드만 남김
|
||||
$targetFields = array_values(array_intersect($targetFields, array_keys($allFormRules)));
|
||||
|
||||
// 최종: modify에서는 "타겟만" 룰/라벨 세팅
|
||||
$formRules = $this->getFormRules($targetFields);
|
||||
$formFields = $this->getFormFields($targetFields);
|
||||
$formRules = $this->getFormRules($targetFields);
|
||||
// throw new RuntimeException(static::class . "->targetFields" . implode(",", $targetFields) . "\nformRules:" . implode(",", array_keys($formRules)) . "\nformFields:" . implode(",", array_keys($formFields)));
|
||||
}//modify
|
||||
// 2) 액션별 "검증 대상 필드" 결정
|
||||
// $formFields = $allFormFields;
|
||||
// $formRules = $allFormRules;
|
||||
return array($formFields, $formRules, $allFormFields);
|
||||
}
|
||||
final public function validate(array &$formDatas, $isDebug = false): void
|
||||
{
|
||||
log_message('debug', '>>> CommonForm::validate CALLED: ' . static::class . ', formAction:' . $this->formAction);
|
||||
try {
|
||||
// 0) 데이터 구조 정리 (null 변환 X)
|
||||
$formDatas = $this->sanitizeFormDatas($formDatas);
|
||||
list($formFields, $formRules, $allFormFields) = $this->validate_process($formDatas, $isDebug);
|
||||
if (empty($formRules)) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: 검증할 대상 RULE이 없습니다.");
|
||||
}
|
||||
|
||||
// 3) wildcard(role.*) 부모 배열 보정
|
||||
$this->ensureParentArrayForWildcardRules($formDatas, $formRules);
|
||||
|
||||
// 4) numeric(FK 포함) 필드: '' -> null, 숫자 문자열 -> int
|
||||
$numericFields = $this->collectNumericFieldsFromRules($formRules);
|
||||
$formDatas = $this->normalizeNumericEmptyToNull($formDatas, $numericFields);
|
||||
|
||||
// 5) dynamicRules 구성
|
||||
$dynamicRules = [];
|
||||
foreach ($formRules as $field => $rule) {
|
||||
[$fieldName, $ruleStr] = $this->getValidationRule((string) $field, (string) $rule);
|
||||
|
||||
// label 결정
|
||||
if (isset($formFields[$fieldName])) {
|
||||
$label = $formFields[$fieldName];
|
||||
@ -370,13 +312,18 @@ abstract class CommonForm
|
||||
} else {
|
||||
$label = $fieldName;
|
||||
}
|
||||
|
||||
$dynamicRules[$fieldName] = [
|
||||
'label' => $label,
|
||||
'rules' => $ruleStr,
|
||||
];
|
||||
}
|
||||
|
||||
if ($isDebug) {
|
||||
log_message('debug', static::class . '->' . __FUNCTION__ . "에서 Validate dynamicRules DEBUG");
|
||||
log_message('debug', var_export($formDatas, true));
|
||||
log_message('debug', var_export($dynamicRules, true));
|
||||
}
|
||||
|
||||
$this->_validation->setRules($dynamicRules);
|
||||
if (!$this->_validation->run($formDatas)) {
|
||||
$errors = $this->_validation->getErrors();
|
||||
@ -394,7 +341,7 @@ abstract class CommonForm
|
||||
if ($e instanceof RuntimeException) {
|
||||
throw $e;
|
||||
}
|
||||
throw new RuntimeException("유효성 검사 중 시스템 오류 발생: " . $e->getMessage());
|
||||
// throw new RuntimeException("유효성 검사 중 시스템 오류 발생: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -43,6 +43,9 @@ class ServiceForm extends CustomerForm
|
||||
'status'
|
||||
];
|
||||
switch ($action) {
|
||||
case 'modify':
|
||||
$fields = [...$fields, 'amount'];
|
||||
break;
|
||||
case 'view':
|
||||
$fields = [...$fields, 'created_at'];
|
||||
break;
|
||||
|
||||
@ -232,6 +232,10 @@ abstract class CommonService
|
||||
}
|
||||
|
||||
//생성용
|
||||
protected function create_process_validate(CommonForm &$actionForm, array $formDatas)
|
||||
{
|
||||
$actionForm->validate($formDatas); // ✅ 여기서 검증
|
||||
}
|
||||
protected function create_process(array $formDatas): CommonEntity
|
||||
{
|
||||
try {
|
||||
@ -240,7 +244,7 @@ abstract class CommonService
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: actionForm이 정의되지 않았습니다.");
|
||||
}
|
||||
$actionForm->form_init_process('create', $formDatas);
|
||||
$actionForm->validate($formDatas); // ✅ 여기서 검증
|
||||
$this->create_process_validate($actionForm, $formDatas);
|
||||
// 검증 통과 후 엔티티 반영용
|
||||
foreach ($formDatas as $field => $value) {
|
||||
$formDatas = $this->fieldhook_process($field, $value, $formDatas);
|
||||
@ -273,6 +277,10 @@ abstract class CommonService
|
||||
}
|
||||
|
||||
//수정용
|
||||
protected function modify_process_validate(CommonForm &$actionForm, array $formDatas)
|
||||
{
|
||||
$actionForm->validate($formDatas); // ✅ 여기서 검증
|
||||
}
|
||||
protected function modify_process($entity, array $formDatas): CommonEntity
|
||||
{
|
||||
try {
|
||||
@ -281,7 +289,7 @@ abstract class CommonService
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: actionForm이 정의되지 않았습니다.");
|
||||
}
|
||||
$actionForm->form_init_process('modify', $formDatas);
|
||||
$actionForm->validate($formDatas); // ✅ 여기서 검증
|
||||
$this->modify_process_validate($actionForm, $formDatas);
|
||||
// 검증 통과 후 엔티티 반영
|
||||
foreach ($formDatas as $field => $value) {
|
||||
$formDatas = $this->fieldhook_process($field, $value, $formDatas);
|
||||
|
||||
@ -2,14 +2,15 @@
|
||||
|
||||
namespace App\Services\Customer;
|
||||
|
||||
use App\Entities\CommonEntity;
|
||||
use App\Entities\Customer\ServiceEntity;
|
||||
use App\Forms\CommonForm;
|
||||
use App\Forms\Customer\ServiceForm;
|
||||
use App\Helpers\Customer\ServiceHelper;
|
||||
use App\Models\Customer\ServiceModel;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeZone;
|
||||
use RuntimeException;
|
||||
use DateTimeImmutable;
|
||||
use App\Entities\CommonEntity;
|
||||
use App\Forms\Customer\ServiceForm;
|
||||
use App\Models\Customer\ServiceModel;
|
||||
use App\Helpers\Customer\ServiceHelper;
|
||||
use App\Entities\Customer\ServiceEntity;
|
||||
|
||||
class ServiceService extends CustomerService
|
||||
{
|
||||
@ -61,46 +62,31 @@ class ServiceService extends CustomerService
|
||||
return $date->format('Y-m-d');
|
||||
}
|
||||
|
||||
private function getCalculatedAmount(int $rack_price, int $line_price, int $sale_price, int $serverinfo_uid): int
|
||||
{
|
||||
$server_amount = service('equipment_serverservice')->getCalculatedAmount($serverinfo_uid);
|
||||
return (int) $server_amount + $rack_price + $line_price - $sale_price;
|
||||
}
|
||||
|
||||
final protected function updateAmount(ServiceEntity $entity): ServiceEntity
|
||||
{
|
||||
$serverUid = $entity->getServerInfoUid();
|
||||
// ✅ 서버 미지정(해지/분리 상태) 방어: 계산 시도 자체를 막는다
|
||||
if (empty($serverUid)) { // null, 0, '' 모두 방어
|
||||
throw new RuntimeException(
|
||||
static::class . '->' . __FUNCTION__
|
||||
. " 오류: 서비스({$entity->getPK()})에 serverinfo_uid가 없습니다. (해지/분리 상태)"
|
||||
);
|
||||
}
|
||||
$entity->amount = $this->getCalculatedAmount(
|
||||
$entity->getRack(),
|
||||
$entity->getLine(),
|
||||
$entity->getSale(),
|
||||
(int) $serverUid
|
||||
);
|
||||
// dd($entity);
|
||||
if (!$this->model->save($entity)) {
|
||||
throw new RuntimeException("금액 업데이트 중 DB 저장 오류: " . implode(', ', $this->model->errors()));
|
||||
}
|
||||
return $entity;
|
||||
}
|
||||
|
||||
final public function updateBillingAt($uid, string $billing_at): ServiceEntity
|
||||
final public function updateBillingAt(int $uid, string $billing_at): CommonEntity
|
||||
{
|
||||
$entity = $this->getEntity($uid);
|
||||
if (!$entity instanceof ServiceEntity) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:{$uid}에 해당하는 서비스정보를 찾을 수 업습니다.");
|
||||
}
|
||||
$formDatas = ['billing_at' => $billing_at];
|
||||
return parent::modify_process($entity, $formDatas);
|
||||
}
|
||||
|
||||
$entity->billing_at = $billing_at;
|
||||
if (!$this->model->save($entity)) {
|
||||
$errors = $this->model->errors();
|
||||
throw new RuntimeException("금액 업데이트 중 DB 저장 오류: " . implode(', ', $errors));
|
||||
//서비스 관련 총 금액
|
||||
private function getCalculatedAmount(ServiceEntity $entity): int
|
||||
{
|
||||
//서버 관련 금액
|
||||
$server_amount = service('equipment_serverservice')->getCalculatedAmount($entity->getServerInfoUid());
|
||||
return (int) $server_amount + (int) $entity->getRack() + (int) $entity->getLine() - $entity->getSale();
|
||||
}
|
||||
|
||||
//서비스 금액 설정
|
||||
final public function recalcAmount(ServiceEntity $entity): ServiceEntity
|
||||
{
|
||||
$formDatas = ['amount' => $this->getCalculatedAmount($entity)];
|
||||
$entity = parent::modify_process($entity, $formDatas);
|
||||
if (!$entity instanceof ServiceEntity) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:Return Type은 ServiceEntity만 가능");
|
||||
}
|
||||
return $entity;
|
||||
}
|
||||
@ -110,74 +96,74 @@ class ServiceService extends CustomerService
|
||||
return $entity;
|
||||
}
|
||||
|
||||
// ✅ (internal) payment까지 동기화하는 일반용 로직 (트랜잭션 없음)
|
||||
final public function recalcAmountAndSyncPaymentInternal(ServiceEntity $old, ServiceEntity $current): ServiceEntity
|
||||
{
|
||||
$current = $this->updateAmount($current);
|
||||
service('paymentservice')->setByService($old, $current);
|
||||
return $current;
|
||||
}
|
||||
|
||||
// ✅ (internal) amount만 재계산 (해지 시 0원될 수 있으나 payment 건드리지 않음)
|
||||
final public function recalcAmountInternal(ServiceEntity $current): ServiceEntity
|
||||
{
|
||||
return $this->updateAmount($current);
|
||||
}
|
||||
|
||||
// ✅ (public) dbTransaction 보류: 내부 로직만 호출
|
||||
final public function recalcAmountAndSyncPayment(ServiceEntity $serviceEntity): ServiceEntity
|
||||
{
|
||||
$old = clone $serviceEntity;
|
||||
return $this->recalcAmountAndSyncPaymentInternal($old, $serviceEntity);
|
||||
}
|
||||
|
||||
final public function recalcAmountAndSyncPaymentByOld(ServiceEntity $old, ServiceEntity $current): ServiceEntity
|
||||
{
|
||||
return $this->recalcAmountAndSyncPaymentInternal($old, $current);
|
||||
}
|
||||
|
||||
protected function create_process(array $formDatas): ServiceEntity
|
||||
{
|
||||
if (empty($formDatas['site'])) {
|
||||
if (!array_key_exists('site', $formDatas) || empty($formDatas['site'])) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: 사이트가 지정되지 않았습니다.");
|
||||
}
|
||||
if (empty($formDatas['serverinfo_uid'])) {
|
||||
if (!array_key_exists('serverinfo_uid', $formDatas) || empty($formDatas['serverinfo_uid'])) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: 서버가 지정되지 않았습니다.");
|
||||
}
|
||||
|
||||
$formDatas['code'] = $formDatas['site'] . "_s" . uniqid();
|
||||
$formDatas['amount'] = 0;
|
||||
|
||||
$entity = parent::create_process($formDatas);
|
||||
if (!$entity instanceof ServiceEntity) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:Return Type은 ServiceEntity만 가능");
|
||||
}
|
||||
|
||||
//서버 설정
|
||||
service('equipment_serverservice')->attatchToService($entity, $entity->getServerInfoUid());
|
||||
|
||||
return $this->recalcAmountAndSyncPayment($entity);
|
||||
//서비스 금액 설정
|
||||
$entity = $this->recalcAmount($entity);
|
||||
//결제 추가
|
||||
service('paymentservice')->createByService($entity);
|
||||
return $entity;
|
||||
}
|
||||
|
||||
protected function modify_process_validate(CommonForm &$actionForm, array $formDatas)
|
||||
{
|
||||
$actionForm->validate($formDatas, true); // ✅ 여기서 검증
|
||||
}
|
||||
protected function modify_process($entity, array $formDatas): ServiceEntity
|
||||
{
|
||||
if (empty($formDatas['serverinfo_uid'])) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: 서버가 지정되지 않았습니다.");
|
||||
}
|
||||
|
||||
//기존 정보 저장
|
||||
$oldEntity = clone $entity;
|
||||
$formDatas['code'] = $entity->getCode();
|
||||
$formDatas['amount'] = 0;
|
||||
|
||||
$entity = parent::modify_process($entity, $formDatas);
|
||||
if (!$entity instanceof ServiceEntity) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:Return Type은 ServiceEntity만 가능");
|
||||
}
|
||||
|
||||
//서버 설정
|
||||
if ($oldEntity->getServerInfoUid() !== $entity->getServerInfoUid()) {
|
||||
service('equipment_serverservice')->modifyByService($oldEntity, $entity);
|
||||
}
|
||||
//서비스 금액 설정
|
||||
$entity = $this->recalcAmount($entity);
|
||||
//결제비 설정
|
||||
service('paymentservice')->modifyByService($oldEntity, $entity);
|
||||
return $entity;
|
||||
}
|
||||
|
||||
return $this->recalcAmountAndSyncPaymentByOld($oldEntity, $entity);
|
||||
protected function delete_process($entity): ServiceEntity
|
||||
{
|
||||
if (!$entity instanceof ServiceEntity) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:Return Type은 ServiceEntity만 가능");
|
||||
}
|
||||
|
||||
//기존정보 우선 저장
|
||||
$oldEntity = clone $entity;
|
||||
|
||||
$entity = parent::delete_process($entity);
|
||||
if (!$entity instanceof ServiceEntity) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:Return Type은 ServiceEntity만 가능");
|
||||
}
|
||||
if ($oldEntity->getServerInfoUid()) {
|
||||
service('equipment_serverservice')->deatchFromService($oldEntity->getServerInfoUid());
|
||||
}
|
||||
return $oldEntity;
|
||||
}
|
||||
|
||||
public function history(string|int $uid, string $history): CommonEntity
|
||||
|
||||
@ -93,55 +93,18 @@ class ServerPartService extends EquipmentService
|
||||
return $formDatas;
|
||||
}
|
||||
|
||||
/**
|
||||
* ✅ MONTH 파트 변경 시:
|
||||
* - "서비스가 유지중"이면: (serverinfo_uid 기준) 현재 서버가 붙은 서비스로 amount 재계산 + 월 Payment upsert
|
||||
* - "서버가 서비스에서 분리된 상태(server.serviceinfo_uid==null)"이면: 월 미납 결제 status=TERMINATED (amount 재계산 금지)
|
||||
*
|
||||
* 핵심: ServerPartService는 'serviceUid'가 아니라 'serverUid'를 기준으로 판단한다.
|
||||
*/
|
||||
private function syncMonthlyServiceAndPaymentByServer(
|
||||
int $serverUid,
|
||||
?ServiceEntity $oldServiceSnapshot = null,
|
||||
bool $serverDetached = false
|
||||
): void {
|
||||
$server = service('equipment_serverservice')->getEntity($serverUid);
|
||||
if (!$server instanceof ServerEntity) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: {$serverUid} 서버정보를 찾을수 없습니다.");
|
||||
//서비스 금액 재계산용
|
||||
private function recalcAmount(ServerPartEntity $entity): void
|
||||
{
|
||||
//월비용 서버파트 정보일 경우 서버금액 재계산
|
||||
if ($entity->getBilling() == PAYMENT['BILLING']['MONTH'] && $entity->getServerInfoUid()) {
|
||||
$serverEntity = service('equipment_serverservice')->getEntity($entity->getServerInfoUid());
|
||||
if ($serverEntity instanceof ServerEntity) {
|
||||
service('equipment_serverservice')->recalcAmount($serverEntity);
|
||||
}
|
||||
|
||||
// ✅ 해지 케이스
|
||||
if ($serverDetached) {
|
||||
if ($oldServiceSnapshot instanceof ServiceEntity) {
|
||||
service('paymentservice')->terminateUnpaidMonthlyByService($oldServiceSnapshot);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$serviceUid = $server->getServiceInfoUid();
|
||||
if (!$serviceUid) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: 서비스가 정의되지 않은 서버정보입니다.");
|
||||
}
|
||||
|
||||
$svcService = service('customer_serviceservice');
|
||||
$current = $svcService->getEntity($serviceUid);
|
||||
if (!$current instanceof ServiceEntity) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: {$serviceUid} 서비스정보를 찾을수 없습니다.");
|
||||
}
|
||||
|
||||
// ✅ 핵심 추가: 메인서버만 amount/payment 동기화 허용
|
||||
if ((int) $current->getServerInfoUid() !== (int) $serverUid) {
|
||||
// 대체서버(alternative)에서는 서비스금액/결제 동기화 금지
|
||||
return; // 또는 정책상 필요하면 예외로 변경 가능
|
||||
// throw new RuntimeException("메인서버가 아닌 서버({$serverUid})에서 월동기화 금지");
|
||||
}
|
||||
|
||||
// ✅ 일반 케이스: amount + payment upsert
|
||||
$old = $oldServiceSnapshot instanceof ServiceEntity ? $oldServiceSnapshot : clone $current;
|
||||
$svcService->recalcAmountAndSyncPaymentInternal($old, $current);
|
||||
}
|
||||
|
||||
|
||||
protected function create_process(array $formDatas): CommonEntity
|
||||
{
|
||||
$serverEntity = service('equipment_serverservice')->getEntity($formDatas['serverinfo_uid']);
|
||||
@ -162,7 +125,7 @@ class ServerPartService extends EquipmentService
|
||||
// ✅ 서버가 서비스에 붙어 있을 때만 결제/동기화
|
||||
if ($entity->getServiceInfoUid()) {
|
||||
if ($entity->getBilling() == PAYMENT['BILLING']['MONTH']) {
|
||||
$this->syncMonthlyServiceAndPaymentByServer((int) $entity->getServerInfoUid());
|
||||
$this->recalcAmount($entity);
|
||||
}
|
||||
if ($entity->getBilling() == PAYMENT['BILLING']['ONETIME']) {
|
||||
service('paymentservice')->createByServerPart($entity);
|
||||
@ -178,45 +141,34 @@ class ServerPartService extends EquipmentService
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: {$formDatas['serverinfo_uid']}에 해당하는 서버정보을 찾을수 없습니다.");
|
||||
}
|
||||
|
||||
$formDatas = $this->getFormDatasForServerPart($formDatas, $serverEntity);
|
||||
|
||||
//기존정보 우선 저장
|
||||
$oldEntity = clone $entity;
|
||||
|
||||
$entity = parent::modify_process($entity, $formDatas);
|
||||
$entity = parent::modify_process($entity, $this->getFormDatasForServerPart($formDatas, $serverEntity));
|
||||
if (!$entity instanceof ServerPartEntity) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: Return Type은 ServerPartEntity만 가능");
|
||||
}
|
||||
|
||||
$this->getPartService($entity->getType())->modifyServerPart($oldEntity, $entity);
|
||||
|
||||
// ✅ MONTH 동기화는 serviceinfo_uid가 아니라 serverinfo_uid 기준
|
||||
$serverUidsToSync = [];
|
||||
|
||||
if ($oldEntity->getBilling() == PAYMENT['BILLING']['MONTH'] && $oldEntity->getServerInfoUid()) {
|
||||
$serverUidsToSync[(int) $oldEntity->getServerInfoUid()] = true;
|
||||
}
|
||||
// ✅ 월별용 처리 (서버파트가 변경되었으므로 서버파트의 Billing이 MONTH이면 서비스/결제 동기화)
|
||||
if ($entity->getBilling() == PAYMENT['BILLING']['MONTH'] && $entity->getServerInfoUid()) {
|
||||
$serverUidsToSync[(int) $entity->getServerInfoUid()] = true;
|
||||
$this->recalcAmount($entity);
|
||||
}
|
||||
|
||||
foreach (array_keys($serverUidsToSync) as $serverUid) {
|
||||
$this->syncMonthlyServiceAndPaymentByServer((int) $serverUid);
|
||||
}
|
||||
|
||||
// ✅ 일회성용 처리
|
||||
if ($entity->getBilling() == PAYMENT['BILLING']['ONETIME']) {
|
||||
service('paymentservice')->modifyByServerPart($oldEntity, $entity);
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
protected function delete_process($entity): CommonEntity
|
||||
protected function delete_process($entity): ServerPartEntity
|
||||
{
|
||||
if (!$entity instanceof ServerPartEntity) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: ServerPartEntity만 가능");
|
||||
}
|
||||
|
||||
$old = clone $entity;
|
||||
//기존정보 우선 저장
|
||||
$oldEntity = clone $entity;
|
||||
|
||||
$this->getPartService($entity->getType())->detachFromServerPart($entity);
|
||||
|
||||
@ -224,12 +176,10 @@ class ServerPartService extends EquipmentService
|
||||
if (!$entity instanceof ServerPartEntity) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: Return Type은 ServerPartEntity만 가능");
|
||||
}
|
||||
|
||||
// ✅ MONTH면 서버 기준으로 서비스/결제 동기화
|
||||
if ($old->getBilling() == PAYMENT['BILLING']['MONTH'] && $old->getServerInfoUid()) {
|
||||
$this->syncMonthlyServiceAndPaymentByServer((int) $old->getServerInfoUid());
|
||||
// ✅서버파트가 삭제되었으므로 서버파트의 Billing이 MONTH이면 서비스/결제 동기화
|
||||
if ($oldEntity->getBilling() == PAYMENT['BILLING']['MONTH'] && $oldEntity->getServerInfoUid()) {
|
||||
$this->recalcAmount($oldEntity);
|
||||
}
|
||||
|
||||
// ✅ ONETIME 미납 결제 삭제는 "보류" (여기서는 아무것도 안함)
|
||||
return $entity;
|
||||
}
|
||||
@ -268,37 +218,21 @@ class ServerPartService extends EquipmentService
|
||||
|
||||
/**
|
||||
* ✅ 서버 해지/분리 시 서버파트 회수 처리
|
||||
* - BASE 제외 전부 삭제(정책상 OK)
|
||||
* - MONTH 삭제가 있었다면:
|
||||
* - server.serviceinfo_uid == null 인 상태에서만 TERMINATED 처리 (amount 재계산 금지)
|
||||
* - ONETIME 미납 삭제는 보류
|
||||
*
|
||||
* @param ServiceEntity|null $oldServiceEntity 분리 전 서비스 스냅샷(권장: TERMINATED 매칭용)
|
||||
*/
|
||||
public function detachFromServer(ServerEntity $serverEntity, ?ServiceEntity $oldServiceEntity = null): void
|
||||
public function detachFromServer(ServerEntity $serverEntity): void
|
||||
{
|
||||
$monthlyChanged = false;
|
||||
foreach ($this->getEntities(['serverinfo_uid' => $serverEntity->getPK(), "billing !=" => PAYMENT['BILLING']['BASE']]) as $entity) {
|
||||
if (!$entity instanceof ServerPartEntity)
|
||||
if (!$entity instanceof ServerPartEntity) {
|
||||
continue;
|
||||
}
|
||||
//파트정보 해지
|
||||
$this->getPartService($entity->getType())->detachFromServerPart($entity);
|
||||
if ($entity->getBilling() == PAYMENT['BILLING']['MONTH']) {
|
||||
$monthlyChanged = true;
|
||||
}
|
||||
//서버파트 해지
|
||||
parent::delete_process($entity);
|
||||
}
|
||||
if (!$monthlyChanged) {
|
||||
return;
|
||||
}
|
||||
// ✅ 분리 완료 후(server.serviceinfo_uid == null)에서만 TERMINATED 처리
|
||||
$serverDetached = ($serverEntity->getServiceInfoUid() === null);
|
||||
if ($serverDetached) {
|
||||
if ($oldServiceEntity instanceof ServiceEntity) {
|
||||
$this->syncMonthlyServiceAndPaymentByServer((int) $serverEntity->getPK(), $oldServiceEntity, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// ✅ (이 케이스는 정상 플로우에서는 거의 없지만) 서비스 유지중이면 정상 upsert
|
||||
$this->syncMonthlyServiceAndPaymentByServer((int) $serverEntity->getPK());
|
||||
//서비스금액 재계산
|
||||
if ($entity->getBilling() == PAYMENT['BILLING']['MONTH'] && $entity->getServerInfoUid()) {
|
||||
$this->recalcAmount($entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,6 +25,12 @@ class ServerService extends EquipmentService
|
||||
return ServerEntity::class;
|
||||
}
|
||||
|
||||
protected function getEntity_process(mixed $entity): ServerEntity
|
||||
{
|
||||
return $entity;
|
||||
}
|
||||
|
||||
|
||||
final public function getTotalServiceCount(array $where = []): array
|
||||
{
|
||||
$totalCounts = [
|
||||
@ -100,18 +106,29 @@ class ServerService extends EquipmentService
|
||||
if (!$entity instanceof ServerEntity) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: {$uid} 서버 정보를 찾을수 없습니다.");
|
||||
}
|
||||
$serverPartService = service('equipment_serverpartservice');
|
||||
$caculatedAmount = $entity->getPrice();
|
||||
foreach ($serverPartService->getEntities(['serverinfo_uid' => $entity->getPK(), 'billing' => PAYMENT['BILLING']['MONTH']]) as $serverPartEntity) {
|
||||
foreach (service('equipment_serverpartservice')->getEntities(['serverinfo_uid' => $entity->getPK(), 'billing' => PAYMENT['BILLING']['MONTH']]) as $serverPartEntity) {
|
||||
log_message('debug', $serverPartEntity->getCustomTitle() . '::' . $serverPartEntity->getCalculatedAmount());
|
||||
$caculatedAmount += $serverPartEntity->getCalculatedAmount();
|
||||
}
|
||||
return $caculatedAmount;
|
||||
}
|
||||
|
||||
protected function getEntity_process(mixed $entity): ServerEntity
|
||||
public function recalcAmount(ServerEntity $entity): void
|
||||
{
|
||||
return $entity;
|
||||
if ($entity->getServiceInfoUid()) {
|
||||
$serviceEntity = service('customer_serviceservice')->getEntity($entity->getServiceInfoUid());
|
||||
if (!$serviceEntity instanceof ServiceEntity) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: {$entity->getServiceInfoUid()} 서비스정보를 찾을수 없습니다.");
|
||||
}
|
||||
// ✅ 핵심 추가: 서비스금액 변경 및 payment Sync용
|
||||
if ($serviceEntity->getServerInfoUid() === $entity->getServiceInfoUid()) {
|
||||
$oldServiceEntity = clone $serviceEntity;
|
||||
$serviceEntity = service('customer_serviceservice')->recalcAmount($serviceEntity);
|
||||
//결제비 설정
|
||||
service('paymentservice')->modifyByService($oldServiceEntity, $serviceEntity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function create_process(array $formDatas): ServerEntity
|
||||
@ -120,22 +137,14 @@ class ServerService extends EquipmentService
|
||||
if (!$entity instanceof ServerEntity) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:Return Type은 ServerEntity만 가능");
|
||||
}
|
||||
|
||||
try {
|
||||
if ($entity->getIP()) {
|
||||
service('part_ipservice')->attachToServer($entity);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
log_message('debug', static::class . '->' . __FUNCTION__ . '에서:' . $e->getMessage());
|
||||
}
|
||||
|
||||
if ($entity->getSwitchInfoUid()) {
|
||||
service('part_switchservice')->attachToServer($entity);
|
||||
}
|
||||
|
||||
service('equipment_chassisservice')->attachToServer($entity);
|
||||
service('equipment_serverpartservice')->attachToServer($entity);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
@ -145,23 +154,21 @@ class ServerService extends EquipmentService
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . '에서 오류발생: 샷시정보가 정의되지 않았습니다.');
|
||||
}
|
||||
|
||||
//기존정보 저장
|
||||
$oldEntity = clone $entity;
|
||||
|
||||
$entity = parent::modify_process($entity, $formDatas);
|
||||
if (!$entity instanceof ServerEntity) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:Return Type은 ServerEntity만 가능");
|
||||
}
|
||||
|
||||
if ($oldEntity->getIP() !== $entity->getIP()) {
|
||||
try {
|
||||
if ($oldEntity->getIP()) {
|
||||
service('part_ipservice')->detachFromServer($oldEntity);
|
||||
}
|
||||
if ($entity->getIP()) {
|
||||
service('part_ipservice')->attachToServer($entity);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
log_message('debug', static::class . '->' . __FUNCTION__ . '에서:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if ($oldEntity->getSwitchInfoUid() !== $entity->getSwitchInfoUid()) {
|
||||
@ -179,22 +186,51 @@ class ServerService extends EquipmentService
|
||||
service('equipment_chassisservice')->attachToServer($entity);
|
||||
}
|
||||
}
|
||||
|
||||
//가격 변동이 있는 경우
|
||||
if ($oldEntity->getPrice() !== $entity->getPrice()) {
|
||||
// ✅ 서비스 유지중이면 정상 동기화 (해지 시는 detachFromService에서 따로 처리)
|
||||
if ($entity->getServiceInfoUid() !== null) {
|
||||
$serviceService = service('customer_serviceservice');
|
||||
$serviceEntity = $serviceService->getEntity($entity->getServiceInfoUid());
|
||||
if (!$serviceEntity instanceof ServiceEntity) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: {$entity->getServiceInfoUid()}에 해당하는 서비스정보을 찾을수 없습니다.");
|
||||
}
|
||||
$serviceService->recalcAmountAndSyncPayment($serviceEntity);
|
||||
}
|
||||
$this->recalcAmount($entity);
|
||||
}
|
||||
return $entity;
|
||||
}
|
||||
|
||||
protected function delete_process($entity): ServerEntity
|
||||
{
|
||||
if (!$entity instanceof ServerEntity) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:Return Type은 ServiceEntity만 가능");
|
||||
}
|
||||
|
||||
//기존정보 우선 저장
|
||||
$oldEntity = clone $entity;
|
||||
|
||||
//서버정보
|
||||
$entity = parent::delete_process($entity);
|
||||
if (!$entity instanceof ServerEntity) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:Return Type은 ServiceEntity만 가능");
|
||||
}
|
||||
|
||||
//서비스연결이 유지된 상태인경우
|
||||
if ($entity->getServiceInfoUid() !== null) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:{$entity->getTitle()} 서버는 아직 서비스와 연결된 상태입니다.");
|
||||
}
|
||||
//IP정보
|
||||
if ($oldEntity->getIP()) {
|
||||
service('part_ipservice')->detachFromServer($oldEntity);
|
||||
}
|
||||
//Switch정보
|
||||
if ($oldEntity->getSwitchInfoUid()) {
|
||||
if (is_int($oldEntity->getSwitchInfoUid())) { //null이거나 공백인경우
|
||||
service('part_switchservice')->detachFromServer($oldEntity);
|
||||
}
|
||||
}
|
||||
//샷시정보
|
||||
if ($oldEntity->getChassisInfoUid()) {
|
||||
service('equipment_chassisservice')->detachFromServer($oldEntity);
|
||||
}
|
||||
//파트정보
|
||||
service('equipment_serverpartservice')->detachFromServer($oldEntity);
|
||||
return $oldEntity;
|
||||
}
|
||||
|
||||
public function setSearchWord(string $word): void
|
||||
{
|
||||
$this->model->orLike($this->model->getTable() . '.ip', $word, 'both');
|
||||
@ -208,11 +244,9 @@ class ServerService extends EquipmentService
|
||||
if (!$entity instanceof ServerEntity) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: {$uid}에 해당하는 서버정보을 찾을수 없습니다.");
|
||||
}
|
||||
|
||||
$formDatas['serviceinfo_uid'] = $serviceEntity->getPK();
|
||||
$formDatas["clientinfo_uid"] = $serviceEntity->getClientInfoUid();
|
||||
$formDatas['status'] = $formDatas['status'] ?? STATUS['OCCUPIED'];
|
||||
|
||||
parent::modify_process($entity, $formDatas);
|
||||
}
|
||||
|
||||
|
||||
@ -93,41 +93,6 @@ class PaymentService extends CommonService
|
||||
return $walletService;
|
||||
}
|
||||
|
||||
// ✅ 서비스 해지(서버 분리) 시: 월 미납 청구는 "삭제/0원수정" 금지, 상태만 TERMINATED
|
||||
public function terminateUnpaidMonthlyByService(ServiceEntity $oldServiceEntity): void
|
||||
{
|
||||
$entity = $this->getEntity([
|
||||
'serviceinfo_uid' => $oldServiceEntity->getPK(),
|
||||
'billing' => PAYMENT['BILLING']['MONTH'],
|
||||
'billing_at' => $oldServiceEntity->getBillingAt(),
|
||||
'status' => STATUS['UNPAID'],
|
||||
]);
|
||||
|
||||
if (!$entity instanceof PaymentEntity) {
|
||||
return;
|
||||
}
|
||||
|
||||
// amount/title 건드리지 않고 status만 변경
|
||||
parent::modify_process($entity, ['status' => STATUS['TERMINATED']]);
|
||||
}
|
||||
|
||||
//서비스정보로 결제정보 생성 또는 수정 (일반 운영용: upsert 유지)
|
||||
public function setByService(ServiceEntity $oldServiceEntity, ServiceEntity $serviceEntity): PaymentEntity
|
||||
{
|
||||
$formDatas = $this->getFormDatasFromService($serviceEntity);
|
||||
$entity = $this->getEntity([
|
||||
'serviceinfo_uid' => $oldServiceEntity->getPK(),
|
||||
'billing' => PAYMENT['BILLING']['MONTH'],
|
||||
'billing_at' => $oldServiceEntity->getBillingAt(),
|
||||
'status' => STATUS['UNPAID']
|
||||
]);
|
||||
//매칭되는게 있으면
|
||||
if ($entity instanceof PaymentEntity) {
|
||||
return $this->modify_process($entity, $formDatas);
|
||||
}
|
||||
return $this->create_process($formDatas);
|
||||
}
|
||||
|
||||
//일회성,선결제,쿠폰,포인트 입력 관련
|
||||
protected function create_process(array $formDatas): PaymentEntity
|
||||
{
|
||||
@ -251,6 +216,23 @@ class PaymentService extends CommonService
|
||||
$formDatas = $this->getFormDatasFromService($serviceEntity);
|
||||
return $this->create_process($formDatas);
|
||||
}
|
||||
//서비스정보로 결제정보 생성 또는 수정 (일반 운영용: upsert 유지)
|
||||
public function modifyByService(ServiceEntity $oldServiceEntity, ServiceEntity $serviceEntity): PaymentEntity
|
||||
{
|
||||
$entity = $this->getEntity([
|
||||
'serviceinfo_uid' => $oldServiceEntity->getPK(),
|
||||
'billing' => PAYMENT['BILLING']['MONTH'],
|
||||
'billing_at' => $oldServiceEntity->getBillingAt(),
|
||||
'status' => STATUS['UNPAID']
|
||||
]);
|
||||
if (!$entity instanceof PaymentEntity) {
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:Return Type은 ServiceEntity만 가능");
|
||||
}
|
||||
//매칭되는게 있으면(기존 서비스인경우) 아니면 신규등록
|
||||
$formDatas = $this->getFormDatasFromService($serviceEntity);
|
||||
return $this->modify_process($entity, $formDatas);
|
||||
}
|
||||
|
||||
|
||||
//서버파트별 일회성 관련
|
||||
private function getFormDatasFromServerPart(ServerPartEntity $serverPartEntity, array $formDatas = []): array
|
||||
@ -287,15 +269,26 @@ class PaymentService extends CommonService
|
||||
]);
|
||||
|
||||
if (!$entity instanceof PaymentEntity) {
|
||||
log_message('error', sprintf(
|
||||
"\n------Last Query (%s)-----\nQuery: %s\n------------------------------\n",
|
||||
static::class . '->' . __FUNCTION__,
|
||||
$this->model->getLastQuery() ?? "No Query Available",
|
||||
));
|
||||
throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: 기존 서버파트정보의 {$oldServerPartEntity->getTitle()}에 해당하는 결제정보가 존재하지 않습니다.");
|
||||
}
|
||||
|
||||
$formDatas = $this->getFormDatasFromServerPart($serverPartEntity);
|
||||
return parent::modify_process($entity, $formDatas);
|
||||
}
|
||||
|
||||
// ✅ 서비스 해지(서버 분리) 시: 월비용 파트정보는 고객만 연결
|
||||
public function terminateByServerPart(ServiceEntity $oldServiceEntity): void
|
||||
{
|
||||
$entity = $this->getEntity([
|
||||
'serviceinfo_uid' => $oldServiceEntity->getPK(),
|
||||
'billing' => PAYMENT['BILLING']['MONTH'],
|
||||
'billing_at' => $oldServiceEntity->getBillingAt(),
|
||||
'status' => STATUS['UNPAID'],
|
||||
]);
|
||||
if (!$entity instanceof PaymentEntity) {
|
||||
return;
|
||||
}
|
||||
//매칭되는게 있으면(기존 파트정보서비스인경우)
|
||||
parent::modify_process($entity, ['status' => STATUS['TERMINATED']]);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user