addClassPaths('Service'); } public function getEntityClass(): string { return ServiceEntity::class; } final public function getNewServiceEntities(int $interval, string $status = STATUS['AVAILABLE']): array { return $this->getEntities(["start_at >= NOW()-INTERVAL {$interval} DAY" => null, "status" => $status]); } final public function getTotalAmounts($where = []): array { $rows = $this->model->groupBy('clientinfo_uid')->select("clientinfo_uid,SUM(amount) AS amount") ->where($where) ->get()->getResult(); $amounts = []; foreach ($rows as $row) { $amounts[$row->clientinfo_uid] = $row->amount; } return $amounts; } final public function getNextMonthDate(ServiceEntity $entity): string { $date = new DateTimeImmutable($entity->getBillingAt(), new DateTimeZone('Asia/Tokyo')); $day = (int) $date->format('d'); $date = $date->modify('first day of next month'); $lastDayOfNextMonth = (int) $date->format('t'); if ($day > $lastDayOfNextMonth) { $day = $lastDayOfNextMonth; } $date = $date->setDate((int) $date->format('Y'), (int) $date->format('m'), $day); 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 { $entity = $this->getEntity($uid); if (!$entity instanceof ServiceEntity) { throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생:{$uid}에 해당하는 서비스정보를 찾을 수 업습니다."); } $entity->billing_at = $billing_at; if (!$this->model->save($entity)) { $errors = $this->model->errors(); throw new RuntimeException("금액 업데이트 중 DB 저장 오류: " . implode(', ', $errors)); } return $entity; } protected function getEntity_process(mixed $entity): ServiceEntity { 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'])) { throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: 사이트가 지정되지 않았습니다."); } if (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); } 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); } return $this->recalcAmountAndSyncPaymentByOld($oldEntity, $entity); } public function history(string|int $uid, string $history): CommonEntity { return $this->dbTransaction(function () use ($uid, $history) { $entity = $this->getEntity($uid); if (!$entity) { throw new RuntimeException(static::class . '->' . __FUNCTION__ . "에서 오류발생: {$uid}에 해당하는 정보을 찾을수 없습니다."); } $formDatas['user_uid'] = (int) $this->getAuthContext()->getUID(); $formDatas['history'] = $history; // 검증 통과 후 엔티티 반영 $entity->fill($formDatas); if (!$entity->hasChanged()) { return $entity; } return $this->save_process($entity); }, __FUNCTION__); } }