krgm.so-manager-dev.com/app/Http/Controllers/Admin/TaxController.php

285 lines
9.8 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Tax;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpFoundation\StreamedResponse;
class TaxController extends Controller
{
/**
* 一覧:キーワード/適用日範囲で絞り込み + ソート + ページング
*/
public function list(Request $request)
{
$query = Tax::query();
// 絞り込み
$keyword = trim((string) $request->input('kw'));
if ($keyword !== '') {
// 数値型でも互換のため部分一致を残す
$query->where('tax_percent', 'like', "%{$keyword}%");
}
$from = $request->input('from');
$to = $request->input('to');
if ($from) {
$query->whereDate('tax_day', '>=', $from);
}
if ($to) {
$query->whereDate('tax_day', '<=', $to);
}
// ソート(既定:適用日 降順)
$sort = $request->input('sort', 'tax_day');
$type = strtolower($request->input('sort_type', 'desc'));
$allow = ['tax_day', 'tax_percent', 'updated_at', 'created_at', 'tax_id'];
if (!in_array($sort, $allow, true)) {
$sort = 'tax_day';
}
if (!in_array($type, ['asc', 'desc'], true)) {
$type = 'desc';
}
$query->orderBy($sort, $type);
$list = $query->paginate(20)->appends($request->except('page'));
return view('admin.tax.list', [
'taxes' => $list,
'kw' => $keyword,
'from' => $from,
'to' => $to,
'sort' => $sort,
'sort_type' => $type,
]);
}
public function add(Request $request)
{
if ($request->isMethod('post')) {
$data = $request->validate([
'tax_percent' => ['required', 'numeric', 'min:0', 'max:1000'],
'tax_day' => ['required', 'date', 'unique:tax,tax_day'],
]);
$data['operator_id'] = optional(\Auth::user())->ope_id ?? null;
$data['tax_percent'] = number_format((float)$data['tax_percent'], 2, '.', '');
\App\Models\Tax::create($data);
return redirect()->route('tax')->with('success', '登録しました');
}
return view('admin.tax.add', [
'tax' => null,
'isEdit' => false,
'isInfo' => false,
]);
}
public function edit(int $tax_id, Request $request)
{
$tax = \App\Models\Tax::findOrFail($tax_id);
if ($request->isMethod('post')) {
$data = $request->validate([
'tax_percent' => ['required', 'numeric', 'min:0', 'max:1000'],
'tax_day' => ['required', 'date', 'unique:tax,tax_day,' . $tax->tax_id . ',tax_id'],
]);
$data['operator_id'] = optional(\Auth::user())->ope_id ?? null;
$data['tax_percent'] = number_format((float)$data['tax_percent'], 2, '.', '');
$tax->update($data);
return redirect()->route('tax')->with('success', '更新しました');
}
return view('admin.tax.edit', [
'tax' => $tax,
'isEdit' => true,
'isInfo' => false,
]);
}
public function info(int $tax_id)
{
$tax = \App\Models\Tax::findOrFail($tax_id);
return view('admin.tax.info', [
'tax' => $tax,
'isEdit' => false,
'isInfo' => true,
]);
}
/**
* 一括削除(一覧のチェックボックスで送られてくる想定)
* フォーム側 name="ids[]" の配列を POST
*/
public function delete(Request $request)
{
$ids = (array) $request->input('ids', []);
$ids = array_values(array_filter($ids, fn($v) => preg_match('/^\d+$/', (string) $v)));
if (empty($ids)) {
return redirect()->route('tax')->with('error', '削除対象が選択されていません。');
}
Tax::whereIn('tax_id', $ids)->delete();
return redirect()->route('tax')->with('success', '削除しました');
}
/**
* CSVインポート
* カラム想定: tax_percent, tax_day
* - 1行目はヘッダ可
* - tax_day をキーとして「存在すれば更新 / 無ければ作成」
*/
public function import(Request $request)
{
$request->validate([
'file' => ['required', 'file', 'mimetypes:text/plain,text/csv,text/tsv', 'max:2048'],
]);
$path = $request->file('file')->getRealPath();
if (!$path || !is_readable($path)) {
return redirect()->route('tax')->with('error', 'ファイルを読み込めません。');
}
$created = 0;
$updated = 0;
$skipped = 0;
DB::beginTransaction();
try {
if (($fp = fopen($path, 'r')) !== false) {
$line = 0;
while (($row = fgetcsv($fp)) !== false) {
$line++;
// 空行スキップ
if (count($row) === 1 && trim((string) $row[0]) === '') {
continue;
}
// ヘッダ行っぽい場合1行目に 'tax_percent' を含む)
if ($line === 1) {
$joined = strtolower(implode(',', $row));
if (str_contains($joined, 'tax_percent') && str_contains($joined, 'tax_day')) {
continue; // ヘッダスキップ
}
}
// 取り出し(列数が足りない場合スキップ)
$percent = $row[0] ?? null;
$day = $row[1] ?? null;
if ($percent === null || $day === null) {
$skipped++;
continue;
}
// 正規化 & 検証
$percent = trim((string) $percent);
$percent = rtrim($percent, '%');
$percent = preg_replace('/[^\d.]/', '', $percent) ?? '0';
$percentF = (float) $percent;
if ($percentF < 0) {
$skipped++;
continue;
}
$percentF = (float) number_format($percentF, 2, '.', '');
$day = date('Y-m-d', strtotime((string) $day));
if (!$day) {
$skipped++;
continue;
}
// upsert: 適用日ユニーク運用
$existing = Tax::whereDate('tax_day', $day)->first();
$payload = [
'tax_percent' => $percentF,
'tax_day' => $day,
'operator_id' => optional(Auth::user())->ope_id ?? null,
];
if ($existing) {
$existing->update($payload);
$updated++;
} else {
Tax::create($payload);
$created++;
}
}
fclose($fp);
}
DB::commit();
return redirect()->route('tax')->with('success', "インポート完了:新規 {$created} 件、更新 {$updated} 件、スキップ {$skipped}");
} catch (\Throwable $e) {
DB::rollBack();
return redirect()->route('tax')->with('error', 'インポートに失敗しました:' . $e->getMessage());
}
}
/**
* CSVエクスポート現在の絞り込み/ソート条件を反映
*/
public function export(Request $request): StreamedResponse
{
$query = Tax::query();
$keyword = trim((string) $request->input('kw'));
if ($keyword !== '') {
$query->where('tax_percent', 'like', "%{$keyword}%");
}
$from = $request->input('from');
$to = $request->input('to');
if ($from) {
$query->whereDate('tax_day', '>=', $from);
}
if ($to) {
$query->whereDate('tax_day', '<=', $to);
}
$sort = $request->input('sort', 'tax_day');
$type = strtolower($request->input('sort_type', 'desc'));
$allow = ['tax_day', 'tax_percent', 'updated_at', 'created_at', 'tax_id'];
if (!in_array($sort, $allow, true)) {
$sort = 'tax_day';
}
if (!in_array($type, ['asc', 'desc'], true)) {
$type = 'desc';
}
$query->orderBy($sort, $type);
$filename = 'tax_' . now()->format('Ymd_His') . '.csv';
return response()->streamDownload(function () use ($query) {
$out = fopen('php://output', 'w');
// Header設計書の主要カラム
fputcsv($out, ['消費税ID', '消費税率', '適用日', '登録日時', '更新日時', '更新オペレータID']);
$query->chunk(500, function ($rows) use ($out) {
foreach ($rows as $r) {
fputcsv($out, [
$r->tax_id,
// 画面仕様に合わせたい場合は getDisplayTaxPercentAttribute() に置換可
is_numeric($r->tax_percent)
? number_format((float) $r->tax_percent, 2, '.', '')
: (string) $r->tax_percent,
optional($r->tax_day)->format('Y-m-d'),
optional($r->created_at)->format('Y-m-d H:i:s'),
optional($r->updated_at)->format('Y-m-d H:i:s'),
$r->operator_id,
]);
}
});
fclose($out);
}, $filename, [
'Content-Type' => 'text/csv; charset=UTF-8',
]);
}
}