SHJ-9/SHJ-10: 修复定期契約集計処理の統合ロジック

- 定期契約データを psectionusertypemonths で統合
- 新規/更新  減免/通常 を1レコードに集約
- Operator Queue に park_id と operator_id を正確に設定
- SQL に contract_money の SUM を追加
This commit is contained in:
Your Name 2025-10-03 20:32:09 +09:00
parent 3960e062b9
commit 9441a34f6f
10 changed files with 2545 additions and 914 deletions

View File

@ -9,7 +9,7 @@ use App\Services\ShjNineService;
/** /**
* SHJ-9 売上集計処理コマンド * SHJ-9 売上集計処理コマンド
* *
* 駐輪場の売上データを日次・月次・年次で集計する処理を実行する * 駐輪場の売上データを日次で集計する処理を実行する
* バックグラウンドで実行される定期バッチ処理 * バックグラウンドで実行される定期バッチ処理
*/ */
class ShjNineCommand extends Command class ShjNineCommand extends Command
@ -18,19 +18,19 @@ class ShjNineCommand extends Command
* コンソールコマンドの名前とシグネチャ * コンソールコマンドの名前とシグネチャ
* *
* 引数: * 引数:
* - type: 集計種別 (daily/monthly/yearly) (必須) * - type: 集計種別 (daily のみ) (必須)
* - target_date: 集計対象日 (オプション、YYYY-MM-DD形式) * - target_date: 集計対象日 (オプション、YYYY-MM-DD形式)
* *
* @var string * @var string
*/ */
protected $signature = 'shj:9 {type : 集計種別(daily/monthly/yearly)} {target_date? : 集計対象日(YYYY-MM-DD)}'; protected $signature = 'shj:9 {type : 集計種別(daily)} {target_date? : 集計対象日(YYYY-MM-DD)}';
/** /**
* コンソールコマンドの説明 * コンソールコマンドの説明
* *
* @var string * @var string
*/ */
protected $description = 'SHJ-9 売上集計処理 - 日次/月次/年次売上データ集計を実行'; protected $description = 'SHJ-9 売上集計処理 - 日次売上データ集計を実行';
/** /**
* SHJ-9サービスクラス * SHJ-9サービスクラス
@ -138,10 +138,9 @@ class ShjNineCommand extends Command
*/ */
private function validateParameters(string $type, ?string $targetDate): bool private function validateParameters(string $type, ?string $targetDate): bool
{ {
// 集計種別チェック // 集計種別チェックSHJ-9 は日次のみ対応)
$allowedTypes = ['daily', 'monthly', 'yearly']; if ($type !== 'daily') {
if (!in_array($type, $allowedTypes)) { $this->error('SHJ-9 は日次集計dailyのみ対応しています。月次/年次は SHJ-10 を使用してください。');
$this->error('集計種別は daily, monthly, yearly のいずれかを指定してください。');
return false; return false;
} }
@ -157,7 +156,7 @@ class ShjNineCommand extends Command
/** /**
* 集計対象日を決定 * 集計対象日を決定
* *
* @param string $type 集計種別 * @param string $type 集計種別daily 固定)
* @param string|null $targetDate 指定日 * @param string|null $targetDate 指定日
* @return string 集計対象日 * @return string 集計対象日
*/ */
@ -167,23 +166,8 @@ class ShjNineCommand extends Command
return $targetDate; return $targetDate;
} }
// パラメータ指定がない場合のデフォルト設定 // パラメータ指定がない場合は昨日本日の1日前
switch ($type) {
case 'daily':
// 日次昨日本日の1日前
return now()->subDay()->format('Y-m-d'); return now()->subDay()->format('Y-m-d');
case 'monthly':
// 月次:前月の最終日
return now()->subMonth()->endOfMonth()->format('Y-m-d');
case 'yearly':
// 年次:前年の最終日
return now()->subYear()->endOfYear()->format('Y-m-d');
default:
return now()->subDay()->format('Y-m-d');
}
} }
/** /**

View File

@ -48,8 +48,8 @@ class EarningsSummary extends Model
'regular_update_amount', // 更新金額 'regular_update_amount', // 更新金額
'regular_update_reduction_count', // 更新成免件数 'regular_update_reduction_count', // 更新成免件数
'regular_update_reduction_amount', // 更新成免金額 'regular_update_reduction_amount', // 更新成免金額
'turnsum_count', // 残金件数 'lumpsum_count', // 一時金件数
'turnsum', // 残 'lumpsum', // 一時
'refunds', // 解時返戻金 'refunds', // 解時返戻金
'other_income', // 分別収入 'other_income', // 分別収入
'other_spending', // 分別支出 'other_spending', // 分別支出
@ -79,8 +79,8 @@ class EarningsSummary extends Model
'regular_update_amount' => 'decimal:2', 'regular_update_amount' => 'decimal:2',
'regular_update_reduction_count' => 'integer', 'regular_update_reduction_count' => 'integer',
'regular_update_reduction_amount' => 'decimal:2', 'regular_update_reduction_amount' => 'decimal:2',
'turnsum_count' => 'integer', 'lumpsum_count' => 'integer',
'turnsum' => 'decimal:2', 'lumpsum' => 'decimal:2',
'refunds' => 'decimal:2', 'refunds' => 'decimal:2',
'other_income' => 'decimal:2', 'other_income' => 'decimal:2',
'other_spending' => 'decimal:2', 'other_spending' => 'decimal:2',
@ -175,8 +175,8 @@ class EarningsSummary extends Model
SUM(regular_new_amount) as total_new_amount, SUM(regular_new_amount) as total_new_amount,
SUM(regular_update_count) as total_update_count, SUM(regular_update_count) as total_update_count,
SUM(regular_update_amount) as total_update_amount, SUM(regular_update_amount) as total_update_amount,
SUM(turnsum_count) as total_turnsum_count, SUM(lumpsum_count) as total_lumpsum_count,
SUM(turnsum) as total_turnsum, SUM(lumpsum) as total_lumpsum,
SUM(refunds) as total_refunds, SUM(refunds) as total_refunds,
SUM(reissue_count) as total_reissue_count, SUM(reissue_count) as total_reissue_count,
SUM(reissue_amount) as total_reissue_amount SUM(reissue_amount) as total_reissue_amount
@ -249,8 +249,8 @@ class EarningsSummary extends Model
'regular_update_amount' => 0.00, 'regular_update_amount' => 0.00,
'regular_update_reduction_count' => 0, 'regular_update_reduction_count' => 0,
'regular_update_reduction_amount' => 0.00, 'regular_update_reduction_amount' => 0.00,
'turnsum_count' => 0, 'lumpsum_count' => 0,
'turnsum' => 0.00, 'lumpsum' => 0.00,
'refunds' => 0.00, 'refunds' => 0.00,
'other_income' => 0.00, 'other_income' => 0.00,
'other_spending' => 0.00, 'other_spending' => 0.00,
@ -273,7 +273,7 @@ class EarningsSummary extends Model
{ {
return $this->regular_new_amount + return $this->regular_new_amount +
$this->regular_update_amount + $this->regular_update_amount +
$this->turnsum + $this->lumpsum +
$this->reissue_amount + $this->reissue_amount +
$this->other_income - $this->other_income -
$this->other_spending - $this->other_spending -

View File

@ -24,7 +24,7 @@ class HardwareCheckLog extends Model
* *
* @var string * @var string
*/ */
protected $primaryKey = 'log_id'; protected $primaryKey = 'hardware_check_log_id';
/** /**
* 一括代入可能な属性 * 一括代入可能な属性
@ -46,7 +46,7 @@ class HardwareCheckLog extends Model
* @var array * @var array
*/ */
protected $casts = [ protected $casts = [
'log_id' => 'integer', 'hardware_check_log_id' => 'integer',
'device_id' => 'integer', 'device_id' => 'integer',
'status' => 'integer', 'status' => 'integer',
'operator_id' => 'integer', 'operator_id' => 'integer',
@ -220,7 +220,7 @@ class HardwareCheckLog extends Model
{ {
return sprintf( return sprintf(
'HardwareCheckLog[ID:%d, Device:%d, Status:%s, Time:%s]', 'HardwareCheckLog[ID:%d, Device:%d, Status:%s, Time:%s]',
$this->log_id, $this->hardware_check_log_id,
$this->device_id, $this->device_id,
$this->getStatusNameAttribute(), $this->getStatusNameAttribute(),
$this->created_at ? $this->created_at->format('Y-m-d H:i:s') : 'N/A' $this->created_at ? $this->created_at->format('Y-m-d H:i:s') : 'N/A'

View File

@ -0,0 +1,97 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
/**
* 管轄駐輪場モデル - jurisdiction_parkingテーブル
*
* オペレータが管轄する駐輪場の情報を管理
*/
class JurisdictionParking extends Model
{
/**
* テーブル名
*
* @var string
*/
protected $table = 'jurisdiction_parking';
/**
* プライマリキー
*
* @var string
*/
protected $primaryKey = 'jurisdiction_parking_id';
/**
* タイムスタンプ使用
*
* @var bool
*/
public $timestamps = true;
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at';
/**
* 一括代入可能な属性
*
* @var array
*/
protected $fillable = [
'jurisdiction_parking_name',
'ope_id',
'park_id',
'operator_id'
];
/**
* キャストする属性
*
* @var array
*/
protected $casts = [
'jurisdiction_parking_id' => 'integer',
'ope_id' => 'integer',
'park_id' => 'integer',
'operator_id' => 'integer',
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
/**
* オペレータとの関連
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function ope()
{
return $this->belongsTo(Ope::class, 'ope_id', 'ope_id');
}
/**
* 駐輪場との関連
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function park()
{
return $this->belongsTo(Park::class, 'park_id', 'park_id');
}
/**
* 指定駐輪場を管轄するオペレータIDリストを取得
*
* @param int $parkId 駐輪場ID
* @return array オペレータIDの配列
*/
public static function getOperatorIdsByPark(int $parkId): array
{
return self::where('park_id', $parkId)
->pluck('ope_id')
->toArray();
}
}

228
app/Models/Manager.php Normal file
View File

@ -0,0 +1,228 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
/**
* 駐輪場管理者モデル - managerテーブル
*
* 駐輪場管理者マスタ情報を管理するモデル
* 各駐輪場に紐づく管理者の情報、警報送信設定などを保持
*/
class Manager extends Model
{
/**
* テーブル名
*
* @var string
*/
protected $table = 'manager';
/**
* プライマリキー
*
* @var string
*/
protected $primaryKey = 'manager_id';
/**
* タイムスタンプ使用
*
* @var bool
*/
public $timestamps = true;
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at';
/**
* 一括代入可能な属性
*
* @var array
*/
protected $fillable = [
'manager_name', // 管理者名
'manager_type', // 管理者種別
'manager_parkid', // 所属駐輪場ID
'manager_device1', // デバイス1
'manager_device2', // デバイス2
'manager_mail', // メールアドレス
'manager_tel', // 電話番号
'manager_alert1', // アラート送信フラグ1
'manager_alert2', // アラート送信フラグ2
'manager_quit_flag', // 退職フラグ
'manager_quitday', // 退職日
'operator_id' // 登録オペレータID
];
/**
* キャストする属性
*
* @var array
*/
protected $casts = [
'manager_id' => 'integer',
'manager_parkid' => 'integer',
'manager_device1' => 'integer',
'manager_device2' => 'integer',
'manager_alert1' => 'boolean',
'manager_alert2' => 'boolean',
'manager_quit_flag' => 'boolean',
'operator_id' => 'integer',
'manager_quitday' => 'date',
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
/**
* 所属駐輪場との関連
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function park()
{
return $this->belongsTo(Park::class, 'manager_parkid', 'park_id');
}
/**
* 登録オペレータとの関連
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function operator()
{
return $this->belongsTo(Ope::class, 'operator_id', 'ope_id');
}
/**
* アクティブな管理者のみを取得するスコープ
* (退職フラグ = 0
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeActive($query)
{
return $query->where('manager_quit_flag', 0);
}
/**
* メールアドレスが設定されている管理者のみを取得するスコープ
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeHasEmail($query)
{
return $query->whereNotNull('manager_mail')
->where('manager_mail', '!=', '');
}
/**
* 指定駐輪場の管理者を取得するスコープ
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param int $parkId 駐輪場ID
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeByPark($query, int $parkId)
{
return $query->where('manager_parkid', $parkId);
}
/**
* メール送信対象の駐輪場管理者を取得
*
* 条件:
* - 退職フラグ = 0(在職中)
* - メールアドレスが設定されている
*
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function getMailTargetManagers()
{
return self::active()
->hasEmail()
->select(['manager_id', 'manager_name', 'manager_mail', 'manager_parkid'])
->get()
->map(function ($manager) {
return [
'manager_id' => $manager->manager_id,
'name' => $manager->manager_name,
'email' => $manager->manager_mail,
'park_id' => $manager->manager_parkid
];
});
}
/**
* 指定駐輪場のメール送信対象管理者を取得
*
* @param int $parkId 駐輪場ID
* @return \Illuminate\Database\Eloquent\Collection
*/
public static function getMailTargetManagersByPark(int $parkId)
{
return self::active()
->hasEmail()
->byPark($parkId)
->select(['manager_id', 'manager_name', 'manager_mail', 'manager_parkid'])
->get()
->map(function ($manager) {
return [
'manager_id' => $manager->manager_id,
'name' => $manager->manager_name,
'email' => $manager->manager_mail,
'park_id' => $manager->manager_parkid
];
});
}
/**
* 退職しているかどうかを判定
*
* @return bool 退職しているかどうか
*/
public function isQuit(): bool
{
return (bool) $this->manager_quit_flag;
}
/**
* アクティブ(在職中)かどうかを判定
*
* @return bool 在職中かどうか
*/
public function isActive(): bool
{
return !$this->isQuit();
}
/**
* メールアドレスが設定されているかどうかを判定
*
* @return bool メールアドレスが設定されているかどうか
*/
public function hasEmail(): bool
{
return !empty($this->manager_mail);
}
/**
* 文字列表現
*
* @return string
*/
public function __toString(): string
{
return sprintf(
'Manager[ID:%d, Name:%s, Park:%d, Email:%s]',
$this->manager_id,
$this->manager_name,
$this->manager_parkid,
$this->manager_mail ?? 'N/A'
);
}
}

View File

@ -24,7 +24,7 @@ class PrintJobLog extends Model
* *
* @var string * @var string
*/ */
protected $primaryKey = 'log_id'; protected $primaryKey = 'job_log_id';
/** /**
* 一括代入可能な属性 * 一括代入可能な属性
@ -49,7 +49,7 @@ class PrintJobLog extends Model
* @var array * @var array
*/ */
protected $casts = [ protected $casts = [
'log_id' => 'integer', 'job_log_id' => 'integer',
'park_id' => 'integer', 'park_id' => 'integer',
'user_id' => 'integer', 'user_id' => 'integer',
'contract_id' => 'integer', 'contract_id' => 'integer',
@ -242,7 +242,7 @@ class PrintJobLog extends Model
{ {
return sprintf( return sprintf(
'PrintJobLog[ID:%d, Process:%s, ErrorCode:%d, Time:%s]', 'PrintJobLog[ID:%d, Process:%s, ErrorCode:%d, Time:%s]',
$this->log_id, $this->job_log_id,
$this->process_name, $this->process_name,
$this->error_code, $this->error_code,
$this->created_at ? $this->created_at->format('Y-m-d H:i:s') : 'N/A' $this->created_at ? $this->created_at->format('Y-m-d H:i:s') : 'N/A'

85
app/Models/Setting.php Normal file
View File

@ -0,0 +1,85 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
/**
* 設定マスタモデル - settingテーブル
*
* システム全体の設定情報を管理するモデル
*/
class Setting extends Model
{
/**
* テーブル名
*
* @var string
*/
protected $table = 'setting';
/**
* プライマリキー
*
* @var string
*/
protected $primaryKey = 'setting_id';
/**
* タイムスタンプ使用
*
* @var bool
*/
public $timestamps = true;
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at';
/**
* 一括代入可能な属性
*
* @var array
*/
protected $fillable = [
'edit_master',
'web_master',
'auto_change_date',
'auto_chage_master',
're-issue_alert_number',
'image_base_url1',
'image_base_url2',
'printable_alert_flag',
'printable_number',
'printable_alert_number',
'printer_keep_alive',
'operator_id'
];
/**
* キャストする属性
*
* @var array
*/
protected $casts = [
'setting_id' => 'integer',
'printable_alert_flag' => 'boolean',
'printable_number' => 'integer',
'printable_alert_number' => 'integer',
'printer_keep_alive' => 'integer',
'operator_id' => 'integer',
'auto_change_date' => 'datetime',
'created_at' => 'datetime',
'updated_at' => 'datetime'
];
/**
* 設定情報を取得通常はID=1の単一レコード)
*
* @return Setting|null
*/
public static function getSettings(): ?Setting
{
return self::first();
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff