diff --git a/app/Console/Commands/SummaryUse.php b/app/Console/Commands/SummaryUse.php new file mode 100644 index 0000000..497f2c0 --- /dev/null +++ b/app/Console/Commands/SummaryUse.php @@ -0,0 +1,343 @@ +setTargetYYYYMM(); + $this->outputInfo(sprintf("対象集計年月: %s", $this->targetYYYYMM)); + + $db = DBUtil::instance(); + try { + $db->beginTransaction(); + foreach ($this->getTargetContractIds() as $contractId) { + $this->handleTarget($contractId); + } + + $db->commit(); + } catch (Exception $e) { + $db->rollBack(); + throw $e; + } + + + + return self::RESULTCODE_SUCCESS; + } + + private function handleTarget(string $contractId) + { + // 集計対象のモデルを取得 + $model = $this->getModel($contractId); + + // 集計 + $summaryByKeys = $this->summary($model); + + // 保存 + $this->save($model, $summaryByKeys); + } + + private function getTargetContractIds(): array + { + if ($this->option('contractId')) { + + return [$this->option('contractId')]; + } + return Contract::pluck(Contract::COL_NAME_ID)->toArray(); + } + + private function setTargetYYYYMM() + { + $yyyyMM = $this->option('yyyymm'); + + // デフォルト判定 + if ($yyyyMM === null) { + // デフォルトは先月とする + $this->targetDate = DateUtil::now()->addMonth(-1)->setDay(1)->setTime(0, 0); + $this->targetYYYYMM = $this->targetDate->format('Ym'); + return; + } + + // フォーマットチェック + if (!preg_match("/^\d{6}$/", $yyyyMM)) { + throw new Exception('YYYYMM不正'); + } + // 日付チェック + $tmpDate = Carbon::createFromFormat('Ym', $yyyyMM); + if (!$tmpDate->isValid()) { + throw new Exception('YYYYMM 日付不正'); + } + + $this->targetDate = $tmpDate->setDay(1)->setTime(0, 0);; + $this->targetYYYYMM = $this->targetDate->format('Ym');; + } + + private function getModel(string $contractId): UseSummary + { + $model = UseSummary::whereContractId($contractId) + ->whereSummaryYyyymm($this->targetYYYYMM) + ->firstOrNew([ + UseSummary::COL_NAME_CONTRACT_ID => $contractId, + UseSummary::COL_NAME_SUMMARY_YYYYMM => $this->targetYYYYMM, + ]); + + $currentYYYYMM = intval(DateUtil::now()->format('Ym')); + $targetYYYYMM = intval($this->targetYYYYMM); + if ($targetYYYYMM < $currentYYYYMM) { + $model->is_fixed = true; + } + + return $model; + } + + /** + * Undocumented function + * + * @param UseSummary $model + * @return Collection + */ + private function summary(UseSummary $model): Collection + { + + $from = $this->targetDate->clone()->setTime(0, 0); + $to = $this->targetDate->clone()->addMonth()->setTime(0, 0); + + /** + * @var Collection + */ + $summaryByKeys = collect(); + + $makeKey = function (?string $key1, ?string $key2) { + return sprintf("%s-%s", $key1 ?? "NULL", $key2 ?? "NULL"); + }; + + $targetOrders = ReceiptIssuingOrder::getBuilder() + ->where(ReceiptIssuingOrder::COL_NAME_CONTRACT_ID, $model->contract_id) + ->where(ReceiptIssuingOrder::COL_NAME_ORDER_DATETIME, ">=", $from) + ->where(ReceiptIssuingOrder::COL_NAME_ORDER_DATETIME, "<", $to); + + // 領収証発行依頼件数の取得 + $receiptIssuingOrders = DB::table($targetOrders, 'target_orders') + ->groupBy( + ReceiptIssuingOrder::COL_NAME_CONTRACT_ID, + ReceiptIssuingOrder::COL_NAME_SUMMARY_KEY1, + ReceiptIssuingOrder::COL_NAME_SUMMARY_KEY2, + ) + ->select([ + ReceiptIssuingOrder::COL_NAME_CONTRACT_ID, + ReceiptIssuingOrder::COL_NAME_SUMMARY_KEY1, + ReceiptIssuingOrder::COL_NAME_SUMMARY_KEY2, + DB::raw(sprintf("count(*) as count")), + ]) + ->get(); + $model->receipt_order_count = 0; + foreach ($receiptIssuingOrders as $ele) { + + $key1 = data_get($ele, ReceiptIssuingOrder::COL_NAME_SUMMARY_KEY1); + $key2 = data_get($ele, ReceiptIssuingOrder::COL_NAME_SUMMARY_KEY2); + $count = data_get($ele, 'count'); + + $model->receipt_order_count += $count; + + $key = $makeKey($key1, $key2); + + $summaryByKey = $this->getUseByKeyModel($model, $key1, $key2); + + $summaryByKeys->put($key, $summaryByKey); + } + + // SMS送信実績の取得 + $SMSs = SMSSendOrder::getBuilder() + ->where(SMSSendOrder::COL_NAME_CONTRACT_ID, $model->contract_id) + ->whereNotNull(SMSSendOrder::COL_NAME_SEND_DATETIME) + ->where(SMSSendOrder::COL_NAME_SEND_DATETIME, ">=", $from) + ->where(SMSSendOrder::COL_NAME_SEND_DATETIME, "<", $to) + ->where(SMSSendOrder::COL_NAME_DONE, true) + ->groupBy( + ReceiptIssuingOrder::COL_NAME_CONTRACT_ID, + ReceiptIssuingOrder::COL_NAME_SUMMARY_KEY1, + ReceiptIssuingOrder::COL_NAME_SUMMARY_KEY2 + ) + ->select([ + ReceiptIssuingOrder::COL_NAME_CONTRACT_ID, + ReceiptIssuingOrder::COL_NAME_SUMMARY_KEY1, + ReceiptIssuingOrder::COL_NAME_SUMMARY_KEY2, + DB::raw(sprintf("count(*) as count")), + DB::raw(sprintf("sum(%s) as cost", SMSSendOrder::COL_NAME_COST)), + ]) + ->get(); + + $model->sms_send_count = 0; + $model->sms_send_cost = 0; + foreach ($SMSs as $ele) { + $key1 = data_get($ele, SMSSendOrder::COL_NAME_SUMMARY_KEY1); + $key2 = data_get($ele, SMSSendOrder::COL_NAME_SUMMARY_KEY2); + $count = data_get($ele, 'count'); + $cost = data_get($ele, 'cost'); + + $model->sms_send_count += $count; + $model->sms_send_cost += $cost; + + $key = $makeKey($key1, $key2); + + + $summaryByKey = $summaryByKeys->get($key); + if (!$summaryByKey instanceof UseByKeySummary) { + $summaryByKey = $this->getUseByKeyModel($model, $key1, $key2); + $summaryByKeys->put($key, $summaryByKey); + } + + $summaryByKey->fill([ + UseByKeySummary::COL_NAME_SMS_SEND_COUNT => $count, + UseByKeySummary::COL_NAME_SMS_SEND_COST => $cost, + ]); + } + + return $summaryByKeys; + } + + + /** + * @param UseSummary $model + * @param Collection $summaryByKeys + * @return void + */ + private function save(UseSummary $model, Collection $summaryByKeys) + { + + + $contract = $model->contract; + + if (!$contract) { + throw new Exception("契約不正"); + } + + $this->outputInfo( + sprintf( + "集計完了:%s 領収証発行依頼:%d件 SMS送信件数:%d件 SMSコスト:%d円", + $contract->name, + $model->receipt_order_count, + $model->sms_send_count, + $model->sms_send_cost, + ) + ); + + + $model->save(); + + $deleteTargets = UseByKeySummary::whereContractId($model->contract_id) + ->whereSummaryYyyymm($this->targetYYYYMM) + ->get(); + + + foreach ($summaryByKeys as $summary) { + + if (!($summary instanceof UseByKeySummary)) { + throw new LogicException("モデル不正"); + } + + $summary->save(); + + $deleteTargets = $deleteTargets->reject(function ($ele) use ($summary) { + return $ele->summary_key1 === $summary->summary_key1 && $ele->summary_key2 === $summary->summary_key2; + }); + } + + foreach ($deleteTargets as $ele) { + $ele->delete(); + } + } + + private function getUseByKeyModel(UseSummary $model, ?string $key1, ?string $key2): UseByKeySummary + { + + $query = UseByKeySummary::whereContractId($model->contract_id) + ->whereSummaryYyyymm($this->targetYYYYMM); + if ($key1 === null) { + $query->whereNull(UseByKeySummary::COL_NAME_SUMMARY_KEY1); + } else { + $query->whereSummaryKey1($key1); + } + + if ($key2 === null) { + $query->whereNull(UseByKeySummary::COL_NAME_SUMMARY_KEY2); + } else { + $query->whereSummaryKey2($key2); + } + + + $summary = $query->firstOrNew([ + UseByKeySummary::COL_NAME_CONTRACT_ID => $model->contract_id, + UseByKeySummary::COL_NAME_SUMMARY_YYYYMM => $this->targetYYYYMM, + UseByKeySummary::COL_NAME_SUMMARY_KEY1 => $key1, + UseByKeySummary::COL_NAME_SUMMARY_KEY2 => $key2, + ]); + + $currentYYYYMM = intval(DateUtil::now()->format('Ym')); + $targetYYYYMM = intval($this->targetYYYYMM); + if ($targetYYYYMM < $currentYYYYMM) { + $summary->is_fixed = true; + } + + return $summary; + } +} diff --git a/app/Models/BaseModel.php b/app/Models/BaseModel.php index a1c1e99..4ca6967 100644 --- a/app/Models/BaseModel.php +++ b/app/Models/BaseModel.php @@ -35,7 +35,7 @@ abstract class BaseModel extends Model implements IModelFeature public static function getBuilder(string $name = 'main'): Builder { - return DB::table(static::getTableName(), $name); + return DB::table(static::getTableName(), $name)->whereNull(static::COL_NAME_DELETED_AT); } public static function getTableName(): string diff --git a/app/Models/Ex/LoginUser.php b/app/Models/Ex/LoginUser.php index 477f545..b6808cb 100644 --- a/app/Models/Ex/LoginUser.php +++ b/app/Models/Ex/LoginUser.php @@ -58,13 +58,13 @@ class LoginUser Session::put(self::SESSION_KEY_CURERNT_CONTRACT_ID, $contractId); } - public function getCurrentContractId(): string + public function getCurrentContractId(): ?string { $user = $this->user(); if ($user && $user->role === UserRole::SUPER_ADMIN) { - return Session::get(self::SESSION_KEY_CURERNT_CONTRACT_ID, ""); + return Session::get(self::SESSION_KEY_CURERNT_CONTRACT_ID); } - return data_get($user, User::COL_NAME_CONTRACT_ID, ""); + return data_get($user, User::COL_NAME_CONTRACT_ID); } public function getCurrentContract(): ?Contract diff --git a/app/Models/SMSSendOrder.php b/app/Models/SMSSendOrder.php index 895d6bc..329e5fb 100644 --- a/app/Models/SMSSendOrder.php +++ b/app/Models/SMSSendOrder.php @@ -20,6 +20,9 @@ class SMSSendOrder extends AppModel const COL_NAME_CONTENT = "content"; const COL_NAME_PHONE_NUMBER = "phone_number"; + const COL_NAME_SUMMARY_KEY1 = "summary_key1"; + const COL_NAME_SUMMARY_KEY2 = "summary_key2"; + protected $casts = [ self::COL_NAME_PORPOSE => SMSSendPurpose::class, self::COL_NAME_SEND_DATETIME => 'datetime', diff --git a/app/Models/UseByKeySummary.php b/app/Models/UseByKeySummary.php new file mode 100644 index 0000000..94031e0 --- /dev/null +++ b/app/Models/UseByKeySummary.php @@ -0,0 +1,25 @@ + 'datetime', + self::COL_NAME_SUMMARY_DATE_END => 'datetime', + ]; + + public function getModelName(): string + { + return "利用実績集計"; + } +} diff --git a/app/Models/UseSummaryHistory.php b/app/Models/UseSummaryHistory.php new file mode 100644 index 0000000..9e57a14 --- /dev/null +++ b/app/Models/UseSummaryHistory.php @@ -0,0 +1,12 @@ +forHistory) { $this->table->index($columns, $indexName); } else { - $this->table->index([ColumnName::DELETED_AT, ...$columns], $indexName); + $this->table->index([...$columns, ColumnName::DELETED_AT], $indexName); + } + return $this; + } + + public function unique(int $number, array $columns) + { + $uniqueName = $this->getUniqueName($number); + if ($this->forHistory) { + $this->table->unique($columns, $uniqueName); + } else { + $this->table->unique([...$columns, ColumnName::DELETED_AT,], $uniqueName); } return $this; } @@ -104,6 +115,11 @@ class MigrationHelper return sprintf("%s_idx_%02d", $this->table->getTable(), $number); } + private function getUniqueName(int $number) + { + return sprintf("%s_uq_%02d", $this->table->getTable(), $number); + } + public function contractId(bool $nullable = false) { diff --git a/database/migrations/2023_06_07_144200_create_use_summaries_table.php b/database/migrations/2023_06_07_144200_create_use_summaries_table.php new file mode 100644 index 0000000..2663bd4 --- /dev/null +++ b/database/migrations/2023_06_07_144200_create_use_summaries_table.php @@ -0,0 +1,50 @@ +schema()); + MigrationHelper::createTable('use_summary_histories', $this->schema(true)); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('use_summaries'); + Schema::dropIfExists('use_summary_histories'); + } + + private function schema(bool $forHistory = false) + { + + return function (Blueprint $table, MigrationHelper $helper) use ($forHistory) { + $helper->baseColumn() + ->contractId(); + + $table->string("summary_yyyymm")->comment("集計年月"); + $table->unsignedInteger("receipt_order_count")->default(0)->comment("領収証発行依頼件数"); + $table->unsignedInteger("sms_send_count")->default(0)->comment("SMS送信件数"); + $table->unsignedInteger("sms_send_cost")->default(0)->comment("SMS送信コスト"); + $table->boolean('is_fixed')->default(false)->comment('確定済み'); + + if ($forHistory) { + $helper->index(1, [ColumnName::CONTRACT_ID, 'summary_yyyymm']); + } else { + $helper->unique(1, [ColumnName::CONTRACT_ID, 'summary_yyyymm']); + } + }; + } +}; diff --git a/database/migrations/2023_06_08_092700_create_use_by_key_summaries_table.php b/database/migrations/2023_06_08_092700_create_use_by_key_summaries_table.php new file mode 100644 index 0000000..f4043d3 --- /dev/null +++ b/database/migrations/2023_06_08_092700_create_use_by_key_summaries_table.php @@ -0,0 +1,55 @@ +schema()); + MigrationHelper::createTable('use_by_key_summary_histories', $this->schema(true)); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('use_by_key_summaries'); + Schema::dropIfExists('use_by_key_summary_histories'); + } + + private function schema(bool $forHistory = false) + { + + return function (Blueprint $table, MigrationHelper $helper) use ($forHistory) { + $helper->baseColumn() + ->contractId(); + + $table->string("summary_yyyymm")->comment("集計年月"); + $table->string("summary_key1")->comment("集計キー1")->nullable(); + $table->string("summary_key2")->comment("集計キー2")->nullable(); + + $table->unsignedInteger("receipt_order_count")->default(0)->comment("領収証発行依頼件数"); + $table->unsignedInteger("sms_send_count")->default(0)->comment("SMS送信件数"); + $table->unsignedInteger("sms_send_cost")->default(0)->comment("SMS送信コスト"); + $table->boolean('is_fixed')->default(false)->comment('確定済み'); + + + + if ($forHistory) { + $helper->index(1, [ColumnName::CONTRACT_ID, 'summary_yyyymm', 'summary_key1', 'summary_key2']); + } else { + $helper->unique(1, [ColumnName::CONTRACT_ID, 'summary_yyyymm', 'summary_key1', 'summary_key2']); + } + }; + } +};