ラブ上等2 配信日を巡る攻防が、いよいよ佳境を迎えつつある。前作が残した強烈なインパクトと、世界中の視聴者を巻き込んだ空前の熱狂から月日が流れた今も、ファンの渇望は止まらない。画面越しの愛憎劇に胸を焦がしたあの夜から、我々はどれほどこの瞬間を待ち望んだことだろうか。
エンタメ業界の裏側で密やかに噂が囁かれる中、ファンの間ではラブ上等2 配信日を予測する考察合戦が日に日に過熱している。公式からの正式な発表が待たれる一方で、SNS上には決定的な「証拠」とされる投稿が次々と浮上。沈黙を守り続ける制作陣の意図と、水面下で動く巨大プロジェクトの真相に迫った。
前作が残した巨大な足跡とシーズン2への過熱する期待
「普通の恋愛リアリティショーにはもう戻れない」。前作の最終話が配信された直後、タイムラインはその絶賛とロスを訴える声で埋め尽くされた。台本が存在しないからこそ生まれる泥臭い人間模様、剥き出しのエゴ、そして土壇場で見せる真実の愛。過飾を極限まで削ぎ落とした演出スタイルは、瞬く間に国内外の配信ランキング首位を独占した。
2026年を迎えた現在も、その影響力は衰えを見せない。過去作を何度も見返す「リピート視聴」が続いており、新規ファンも増え続けている。これほどのムーブメントを起こした作品の続編が作られないはずがない――業界関係者もファンも、満場一致で「シーズン2」の存在を確信しているのだ。
SNSで拡散された海外ロケ目撃情報!撮影はすでに完了しているのか
火のない所に煙は立たない。今年の初頭から、X(旧Twitter)やInstagramを中心に、南国リゾート地での大規模なロケ撮影の目撃証言が相次いで流出した。カメラクルーの機材に記されたロゴマークや、現地で見かけられた特徴的なセットの配置が、前作のロケ現場と酷似しているというのだ。
「地元のプライベートビーチが数週間にわたって貸し切られていた」「深夜までライトが焚かれ、スタッフの緊迫した声援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援.援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援ええいがあった」という現地の声をスクープした。制作ラインが動き出し、撮影が完了している可能性は極めて高い。現在のポストプロダクション(編集作業)の期間を考慮すれば、情報解禁のタイミングは着々と近づいている。
援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援助援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援援授援援援援援援援援援援援援援援援援助援援援
['target_province'] = implode(', ', $targetProvinces);
}
if (!empty($provinceCategoryName)) {
$data['province_category'] = $provinceCategoryName;
}
RegionPriceAdjustment::create($data);
return redirect()->back()->with('flash_success', '地区差額を登録しました。');
}
/
* Delete region price adjustment.
*
* @param int $id
* @return \Illuminate\Http\RedirectResponse
/
public function destroyPriceAdjustment($id)
{
$adjustment = RegionPriceAdjustment::findOrFail($id);
$adjustment->delete();
return redirect()->back()->with('flash_success', '地区差額を削除しました。');
}
}
backend/app/Http/Controllers/SupportAreaGroupController.php
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$groups = SupportAreaGroup::withCount('prefectures')
->orderBy('sort_order', 'asc')
->orderBy('id', 'asc')
->get();
$prefectures = Prefecture::orderBy('sort_order', 'asc')->get();
return view('admin.support_area_groups.index', compact('groups', 'prefectures'));
}
/
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255|unique:support_area_groups,name',
'code' => 'required|string|max:50|unique:support_area_groups,code|alpha_dash',
'description' => 'nullable|string',
'is_active' => 'boolean',
'prefectures' => 'nullable|array',
'prefectures.' => 'exists:prefectures,id',
]);
$group = SupportAreaGroup::create([
'name' => $request->name,
'code' => strtolower($request->code),
'description' => $request->description,
'is_active' => $request->has('is_active'),
'sort_order' => $request->sort_order ?? 0,
]);
if ($request->has('prefectures')) {
$group->prefectures()->sync($request->prefectures);
}
return redirect()->route('admin.support-area-groups.index')
->with('flash_success', '対応エリアグループを作成しました。');
}
/
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\SupportAreaGroup $supportAreaGroup
* @return \Illuminate\Http\Response
/
public function update(Request $request, SupportAreaGroup $supportAreaGroup)
{
$request->validate([
'name' => [
'required',
'string',
'max:255',
Rule::unique('support_area_groups')->ignore($supportAreaGroup->id),
],
'code' => [
'required',
'string',
'max:50',
'alpha_dash',
Rule::unique('support_area_groups')->ignore($supportAreaGroup->id),
],
'description' => 'nullable|string',
'is_active' => 'boolean',
'prefectures' => 'nullable|array',
'prefectures.' => 'exists:prefectures,id',
]);
$supportAreaGroup->update([
'name' => $request->name,
'code' => strtolower($request->code),
'description' => $request->description,
'is_active' => $request->has('is_active'),
'sort_order' => $request->sort_order ?? $supportAreaGroup->sort_order,
]);
if ($request->has('prefectures')) {
$supportAreaGroup->prefectures()->sync($request->prefectures);
} else {
$supportAreaGroup->prefectures()->detach();
}
return redirect()->route('admin.support-area-groups.index')
->with('flash_success', '対応エリアグループを更新しました。');
}
/
* Remove the specified resource from storage.
*
* @param \App\Models\SupportAreaGroup $supportAreaGroup
* @return \Illuminate\Http\Response
/
public function destroy(SupportAreaGroup $supportAreaGroup)
{
$supportAreaGroup->prefectures()->detach();
$supportAreaGroup->delete();
return redirect()->route('admin.support-area-groups.index')
->with('flash_success', '対応エリアグループを削除しました。');
}
/
* Reorder support area groups.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function reorder(Request $request)
{
$request->validate([
'order' => 'required|array',
'order.' => 'exists:support_area_groups,id',
]);
foreach ($request->order as $index => $id) {
SupportAreaGroup::where('id', $id)->update(['sort_order' => $index + 1]);
}
return response()->json(['status' => 'success']);
}
}
backend/app/Models/Cart.php 'decimal:2',
'total' => 'decimal:2',
'discount_amount' => 'decimal:2',
'expires_at' => 'datetime',
];
/
* Get the user that owns the cart.
/
public function user()
{
return $this->belongsTo(User::class);
}
/
* Get the items in the cart.
*/
public function items()
{
return $this->hasMany(CartItem::class);
}
/
* Recalculate cart totals.
*/
public function recalculateTotals()
{
$subtotal = 0;
foreach ($this->items as $item) {
$subtotal += $item->subtotal;
}
$this->subtotal = $subtotal;
// Calculate total considering discounts
$total = $subtotal - $this->discount_amount;
$this->total = max(0, $total);
$this->save();
return $this;
}
}
backend/app/Http/Controllers/MangaTitleController.php
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
$query = MangaTitle::query();
if ($request->has('search') && !empty($request->search)) {
$search = $request->search;
$query->where(function($q) use ($search) {
$q->where('title', 'like', "%{$search}%")
->orWhere('title_kana', 'like', "%{$search}%")
->orWhere('author', 'like', "%{$search}%")
->orWhere('publisher', 'like', "%{$search}%");
});
}
if ($request->has('is_active') && $request->is_active !== '') {
$query->where('is_active', $request->is_active);
}
$mangaTitles = $query->orderBy('title_kana', 'asc')->paginate(20);
return view('admin.manga_titles.index', compact('mangaTitles'));
}
/
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('admin.manga_titles.create');
}
/
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$request->validate([
'title' => 'required|string|max:255',
'title_kana' => 'nullable|string|max:255',
'author' => 'nullable|string|max:255',
'publisher' => 'nullable|string|max:255',
'total_volumes' => 'nullable|integer|min:1',
'description' => 'nullable|string',
'is_active' => 'boolean',
]);
MangaTitle::create([
'title' => $request->title,
'title_kana' => $request->title_kana,
'author' => $request->author,
'publisher' => $request->publisher,
'total_volumes' => $request->total_volumes,
'description' => $request->description,
'is_active' => $request->has('is_active'),
]);
return redirect()->route('admin.manga-titles.index')
->with('flash_success', '漫画タイトルを登録しました。');
}
/
* Display the specified resource.
*
* @param \App\Models\MangaTitle $mangaTitle
* @return \Illuminate\Http\Response
*/
public function show(MangaTitle $mangaTitle)
{
$mangaTitle->load(['sets' => function($query) {
$query->orderBy('min_volumes', 'asc');
}]);
return view('admin.manga_titles.show', compact('mangaTitle'));
}
/
* Show the form for editing the specified resource.
*
* @param \App\Models\MangaTitle $mangaTitle
* @return \Illuminate\Http\Response
*/
public function edit(MangaTitle $mangaTitle)
{
return view('admin.manga_titles.edit', compact('mangaTitle'));
}
/
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\MangaTitle $mangaTitle
* @return \Illuminate\Http\Response
*/
public function update(Request $request, MangaTitle $mangaTitle)
{
$request->validate([
'title' => 'required|string|max:255',
'title_kana' => 'nullable|string|max:255',
'author' => 'nullable|string|max:255',
'publisher' => 'nullable|string|max:255',
'total_volumes' => 'nullable|integer|min:1',
'description' => 'nullable|string',
'is_active' => 'boolean',
]);
$mangaTitle->update([
'title' => $request->title,
'title_kana' => $request->title_kana,
'author' => $request->author,
'publisher' => $request->publisher,
'total_volumes' => $request->total_volumes,
'description' => $request->description,
'is_active' => $request->has('is_active'),
]);
return redirect()->route('admin.manga-titles.index')
->with('flash_success', '漫画タイトルの情報を更新しました。');
}
/
* Remove the specified resource from storage.
*
* @param \App\Models\MangaTitle $mangaTitle
* @return \Illuminate\Http\Response
*/
public function destroy(MangaTitle $mangaTitle)
{
// 紐付いているセ }
}
ットがあるか確認
if ($mangaTitle->sets()->count() > 0) {
return redirect()->route('admin.manga-titles.index')
->with('flash_error', 'このタイトルにはセット商品が紐付いているため削除できません。先にセット商品を削除するか、非公開に設定してください。');
}
$mangaTitle->delete();
return redirect()->route('admin.manga-titles.index')
->with('flash_success', '漫画タイトルを削除しました。');
backend/app/Models/ContractTypeOptionPrice.php
belongsTo(Option::class);
}
}
nPrice extends Model
{
use HasFactory;
protected $fillable = [
'contract_type_id',
'option_id',
'price',
'price_per_shelf',
'billing_type',
'note',
];
protected $casts = [
'price' => 'decimal:2',
'price_per_shelf' => 'decimal:2',
];
public function contractType()
{
return $this->belongsTo(ContractType::class);
}
public function option()
backend/app/Http/Controllers/OrderManagementController.php
* Display a listing of the orders.
*
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
$query = Order::with(['user', 'orderItems.product']);
// Search by Order Number, Customer Name, or Email
if ($request->filled('search')) {
$search = $request->search;
$query->where(function($q) use ($search) {
$q->where('order_number', 'like', "%{$search}%")
->orWhere('shipping_name', 'like', "%{$search}%")
->orWhere('shipping_email', 'like', "%{$search}%")
->orWhereHas('user', function($u) use ($search) {
$u->where('name', 'like', "%{$search}%")
->orWhere('email', 'like', "%{$search}%");
});
});
}
// Filter by Status
if ($request->filled('status')) {
$query->where('status', $request->status);
}
// Filter by Payment Status
if ($request->filled('payment_status')) {
$query->where('payment_status', $request->payment_status);
}
// Filter by Order Date Range
if ($request->filled('date_from')) {
$query->whereDate('order_date', '>=', $request->date_from);
}
if ($request->filled('date_to')) {
$query->whereDate('order_date', '<=', $request->date_to);
}
$orders = $query->orderBy('order_date', 'desc')
->paginate(20)
->appends($request->all());
return view('admin.orders.index', compact('orders'));
}
/
* Show the form for creating a new order (Admin side entry).
*
* @return \Illuminate\Http\Response
*/
public function create()
{
$prefectures = Prefecture::orderBy('sort_order', 'asc')->get();
$users = User::orderBy('name', 'asc')->get();
$products = Product::where('is_active', true)->orderBy('name', 'asc')->get();
return view('admin.orders.create', compact('prefectures', 'users', 'products'));
}
/
* Store a newly created order in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$request->validate([
'user_id' => 'nullable|exists:users,id',
'shipping_name' => 'required|string|max:255',
'shipping_kana' => 'nullable|string|max:255',
'shipping_postal_code' => 'required|string|max:10',
'shipping_prefecture' => 'required|string|max:50',
'shipping_city' => 'required|string|max:255',
'shipping_address1' => 'required|string|max:255',
'shipping_address2' => 'nullable|string|max:255',
'shipping_phone' => 'required|string|max:30',
'shipping_email' => 'required|email|max:255',
'payment_method' => 'required|string',
'status' => 'required|string',
'payment_status' => 'required|string',
'items' => 'required|array|min:1',
'items..product_id' => 'required|exists:products,id',
'items..quantity' => 'required|integer|min:1',
'items..unit_price' => 'required|numeric|min:0',
]);
DB::beginTransaction();
try {
// Generate Unique Order Number
$orderNumber = 'ORD-' . date('Ymd') . '-' . strtoupper(Str::random(5));
// Calculate Subtotal
$subtotal = 0;
foreach ($request->items as $item) {
$subtotal += $item['unit_price'] * $item['quantity'];
}
$taxAmount = round($subtotal * 0.10); // 10% Tax calculation
$shippingFee = $request->shipping_fee ?? 0;
$discountAmount = $request->discount_amount ?? 0;
$totalAmount = $subtotal + $taxAmount + $shippingFee - $discountAmount;
$order = Order::create([
'order_number' => $orderNumber,
'user_id' => $request->user_id,
'status' => $request->status,
'payment_status' => $request->payment_status,
'payment_method' => $request->payment_method,
'subtotal' => $subtotal,
'tax_amount' => $taxAmount,
'shipping_fee' => $shippingFee,
'discount_amount' => $discountAmount,
'total_amount' => $totalAmount,
'shipping_name' => $request->shipping_name,
'shipping_kana' => $request->shipping_kana,
'shipping_postal_code' => $request->shipping_postal_code,
'shipping_prefecture' => $request->shipping_prefecture,
'shipping_city' => $request->shipping_city,
'shipping_address1' => $request->shipping_address1,
'shipping_address2' => $request->shipping_address2,
'shipping_phone' => $request->shipping_phone,
'shipping_email' => $request->shipping_email,
'admin_notes' => $request->admin_notes,
'customer_notes' => $request->customer_notes,
'order_date' => now(),
]);
foreach ($request->items as $item) {
$product = Product::find($item['product_id']);
$itemSubtotal = $item['unit_price'] * $item['quantity'];
OrderItem::create([
'order_id' => $order->id,
'product_id' => $product->id,
'product_name' => $product->name,
'product_code' => $product->product_code ?? null,
'unit_price' => $item['unit_price'],
'quantity' => $item['quantity'],
'subtotal' => $itemSubtotal,
]);
}
DB::commit();
return redirect()->route('admin.orders.show', $order->id)
->with('flash_success', '注文を登録しました。');
} catch (\Exception $e) {
DB::rollBack();
return redirect()->back()
->withInput()
->with('flash_error', '注文登録中にエラーが発生しました: ' . $e->getMessage());
}
}
/
* Display the specified order details.
*
* @param \App\Models\Order $order
* @return \Illuminate\Http\Response
/
public function show(Order $order)
{
$order->load(['user', 'orderItems.product']);
return view('admin.orders.show', compact('order'));
}
/
* Show the form for editing the specified order.
*
* @param \App\Models\Order $order
* @return \Illuminate\Http\Response
*/
public function edit(Order $order)
{
$order->load(['orderItems.product']);
$prefectures = Prefecture::orderBy('sort_order', 'asc')->get();
$users = User::orderBy('name', 'asc')->get();
$products = Product::where('is_active', true)->orderBy('name', 'asc')->get();
return view('admin.orders.edit', compact('order', 'prefectures', 'users', 'products'));
}
/
* Update the specified order in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\Order $order
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Order $order)
{
$request->validate([
'shipping_name' => 'required|string|max:255',
'shipping_kana' => 'nullable|string|max:255',
'shipping_postal_code' => 'required|string|max:10',
'shipping_prefecture' => 'required|string|max:50',
'shipping_city' => 'required|string|max:255',
'shipping_address1' => 'required|string|max:255',
'shipping_address2' => 'nullable|string|max:255',
'shipping_phone' => 'required|string|max:30',
'shipping_email' => 'required|email|max:255',
'payment_method' => 'required|string',
'status' => 'required|string',
'payment_status' => 'required|string',
'tracking_number' => 'nullable|string|max:255',
'shipping_carrier' => 'nullable|string|max:100',
]);
$order->update([
'status' => $request->status,
'payment_status' => $request->payment_status,
'payment_method' => $request->payment_method,
'tracking_number' => $request->tracking_number,
'shipping_carrier' => $request->shipping_carrier,
'shipping_name' => $request->shipping_name,
'shipping_kana' => $request->shipping_kana,
'shipping_postal_code' => $request->shipping_postal_code,
'shipping_prefecture' => $request->shipping_prefecture,
'shipping_city' => $request->shipping_city,
'shipping_address1' => $request->shipping_address1,
'shipping_address2' => $request->shipping_address2,
'shipping_phone' => $request->shipping_phone,
'shipping_email' => $request->shipping_email,
'admin_notes' => $request->admin_notes,
'customer_notes' => $request->customer_notes,
]);
// If status becomes completed/shipped, set shipped_at date
if (in_array($request->status, ['shipped', 'completed']) && is_null($order->shipped_at)) {
$order->update(['shipped_at' => now()]);
}
return redirect()->route('admin.orders.show', $order->id)
->with('flash_success', '注文情報を更新しました。');
}
/
* Remove the specified order from storage.
*
* @param \App\Models\Order $order
* @return \Illuminate\Http\Response
*/
public function destroy(Order $order)
{
DB::beginTransaction();
try {
$order->orderItems()->delete();
$order->delete();
DB::commit();
return redirect()->route('admin.orders.index')
->with('flash_success', '注文を削除しました。');
} catch (\Exception $e) {
DB::rollBack();
return redirect()->back()
->with('flash_error', '注文削除中にエラーが発生しました。');
}
}
}
backend/app/Models/AreaDetailOptionPrice.php
belongsTo(Prefecture::class);
}
}
ailOptionPrice extends Model
{
use HasFactory;
protected $fillable = [
'option_id',
'prefecture_id',
'city_name',
'price',
'price_per_shelf',
'price_type',
'notes',
];
protected $casts = [
'price' => 'decimal:2',
'price_per_shelf' => 'decimal:2',
];
public function option()
{
return $this->belongsTo(Option::class);
}
backend/app/Http/Controllers/ProductCategoryController.php
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$categories = ProductCategory::with('parent')
->withCount('products')
->orderBy('sort_order', 'asc')
->orderBy('id', 'asc')
->get();
return view('admin.product_categories.index', compact('categories'));
}
/
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
$parentCategories = ProductCategory::whereNull('parent_id')
->orderBy('sort_order', 'asc')
->get();
return view('admin.product_categories.create', compact('parentCategories'));
}
/
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'slug' => 'required|string|max:255|unique:product_categories,slug',
'parent_id' => 'nullable|exists:product_categories,id',
'description' => 'nullable|string',
'sort_order' => 'integer',
'is_active' => 'boolean',
]);
ProductCategory::create([
'name' => $request->name,
'slug' => Str::slug($request->slug),
'parent_id' => $request->parent_id,
'description' => $request->description,
'sort_order' => $request->sort_order ?? 0,
'is_active' => $request->has('is_active'),
]);
return redirect()->route('admin.product-categories.index')
->with('flash_success', '商品カテゴリーを作成しました。');
}
/
* Show the form for editing the specified resource.
*
* @param \App\Models\ProductCategory $productCategory
* @return \Illuminate\Http\Response
*/
public function edit(ProductCategory $productCategory)
{
$parentCategories = ProductCategory::whereNull('parent_id')
->where('id', '!=', $productCategory->id)
->orderBy('sort_order', 'asc')
->get();
return view('admin.product_categories.edit', compact('productCategory', 'parentCategories'));
}
/
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\ProductCategory $productCategory
* @return \Illuminate\Http\Response
*/
public function update(Request $request, ProductCategory $productCategory)
{
$request->validate([
'name' => 'required|string|max:255',
'slug' => [
'required',
'string',
'max:255',
Rule::unique('product_categories')->ignore($productCategory->id),
],
'parent_id' => [
'nullable',
'exists:product_categories,id',
Rule::notIn([$productCategory->id]),
],
'description' => 'nullable|string',
'sort_order' => 'integer',
'is_active' => 'boolean',
]);
$productCategory->update([
'name' => $request->name,
'slug' => Str::slug($request->slug),
'parent_id' => $request->parent_id,
'description' => $request->description,
'sort_order' => $request->sort_order ?? $productCategory->sort_order,
'is_active' => $request->has('is_active'),
]);
return redirect()->route('admin.product-categories.index')
->with('flash_success', '商品カテゴリーを更新しました。');
}
/
* Remove the specified resource from storage.
*
* @param \App\Models\ProductCategory $productCategory
* @return \Illuminate\Http\Response
*/
public function destroy(ProductCategory $productCategory)
{
if ($productCategory->children()->count() > 0) {
return redirect()->route('admin.product-categories.index')
->with('flash_error', '子カテゴリーが存在するため削除できません。');
}
if ($productCategory->products()->count() > 0) {
return redirect()->route('admin.product-categories.index')
->with('flash_error', '商品が紐付いているため削除できません。');
}
);
}
return response()->json(['status' => 'success']);
}
}
$productCategory->delete();
return redirect()->route('admin.product-categories.index')
->with('flash_success', '商品カテゴリーを削除しました。');
}
/
* Reorder product categories via AJAX.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function reorder(Request $request)
{
$request->validate([
'order' => 'required|array',
'order.' => 'exists:product_categories,id',
]);
foreach ($request->order as $index => $id) {
ProductCategory::where('id', $id)->update(['sort_order' => $index + 1]backend/app/Http/Controllers/SiteConfigController.php
* Display a listing of site settings.
/
public function index()
{
$configs = SiteConfig::all()->pluck('value', 'key')->toArray();
return view('admin.site_configs.index', compact('configs'));
}
/
* Update site settings.
*/
public function update(Request $request)
{
$request->validate([
'site_name' => 'required|string|max:255',
'site_email' => 'required|email|max:255',
'site_phone' => 'nullable|string|max:50',
'site_postal_code' => 'nullable|string|max:20',
'site_address' => 'nullable|string|max:255',
'tax_rate' => 'required|numeric|min:0|max:100',
'default_shipping_fee' => 'required|numeric|min:0',
'free_shipping_threshold' => 'nullable|numeric|min:0',
'site_logo' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg,webp|max:2048',
'favicon' => 'nullable|image|mimes:ico,png|max:512',
'meta_title' => 'nullable|string|max:255',
'meta_keywords' => 'nullable|string|max:255',
'meta_description' => 'nullable|string',
'bank_info' => 'nullable|string',
'business_hours' => 'nullable|string',
]);
$inputData = $request->except(['_token', '_method', 'site_logo', 'favicon']);
// Handle Site Logo Upload
if ($request->hasFile('site_logo')) {
$logoPath = $request->file('site_logo')->store('site_assets', 'public');
// Delete old if exists
$oldLogo = SiteConfig::where('key', 'site_logo')->first();
if ($oldLogo && $oldLogo->value) {
Storage::disk('public')->delete($oldLogo->value);
}
$inputData['site_logo'] = $logoPath;
}
// Handle Favicon Upload
if ($request->hasFile('favicon')) {
$faviconPath = $request->file('favicon')->store('site_assets', 'public');
$oldFavicon = SiteConfig::where('key', 'favicon')->first();
if ($oldFavicon && $oldFavicon->value) {
Storage::disk('public')->delete($oldFavicon->value);
}
$inputData['favicon'] = $faviconPath;
}
// Save or update all configurations
foreach ($inputData as $key => $value) {
SiteConfig::updateOrCreate(
['key' => $key],
['value' => $value ?? '']
);
}
return redirect()->route('admin.site-configs.index')
->with('flash_success', 'サイト基本設定を更新しました。');
}
}
backend/app/Models/EstimateItem.php 'decimal:2',
'subtotal' => 'decimal:2',
];
public function estimate()
{
return $this->belongsTo(Estimate::class);
}
}
backend/app/Http/Controllers/ProductController.php
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
$query = Product::with(['category', 'shelf']);
if ($request->has('search') && !empty($request->search)) {
$search = $request->search;
$query->where(function($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('sku', 'like', "%{$search}%")
->orWhere('product_code', 'like', "%{$search}%");
});
}
if ($request->has('category_id') && !empty($request->category_id)) {
$query->where('product_category_id', $request->category_id);
}
if ($request->has('is_active') && $request->is_active !== '') {
$query->where('is_active', $request->is_active);
}
$products = $query->orderBy('id', 'desc')->paginate(20);
$categories = ProductCategory::orderBy('sort_order', 'asc')->get();
return view('admin.products.index', compact('products', 'categories'));
}
/
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
$categories = ProductCategory::orderBy('sort_order', 'asc')->get();
$shelves = Shelf::where('is_active', true)->orderBy('sort_order', 'asc')->get();
return view('admin.products.create', compact('categories', 'shelves'));
}
/
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'sku' => 'nullable|string|max:100|unique:products,sku',
'product_code' => 'nullable|string|max:100',
'product_category_id' => 'nullable|exists:product_categories,id',
'shelf_id' => 'nullable|exists:shelves,id',
'price' => 'required|numeric|min:0',
'sale_price' => 'nullable|numeric|min:0',
'stock_quantity' => 'required|integer|min:0',
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg,webp|max:4096',
'description' => 'nullable|string',
'is_active' => 'boolean',
'is_featured' => 'boolean',
]);
$imagePath = null;
if ($request->hasFile('image')) {
$imagePath = $request->file('image')->store('products', 'public');
}
Product::create([
'name' => $request->name,
'sku' => $request->sku,
'product_code' => $request->product_code,
'product_category_id' => $request->product_category_id,
'shelf_id' => $request->shelf_id,
'price' => $request->price,
'sale_price' => $request->sale_price,
'stock_quantity' => $request->stock_quantity ?? 0,
'image' => $imagePath,
'description' => $request->description,
'is_active' => $request->has('is_active'),
'is_featured' => $request->has('is_featured'),
]);
return redirect()->route('admin.products.index')
->with('flash_success', '商品を登録しました。');
}
/
* Display the specified resource.
*
* @param \App\Models\Product $product
* @return \Illuminate\Http\Response
*/
public function show(Product $product)
{
$product->load(['category', 'shelf']);
return view('admin.products.show', compact('product'));
}
/
* Show the form for editing the specified resource.
*
* @param \App\Models\Product $product
* @return \Illuminate\Http\Response
*/
public function edit(Product $product)
{
$categories = ProductCategory::orderBy('sort_order', 'asc')->get();
$shelves = Shelf::where('is_active', true)->orderBy('sort_order', 'asc')->get();
return view('admin.products.edit', compact('product', 'categories', 'shelves'));
}
/
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\Product $product
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Product $product)
{
$request->validate([
'name' => 'required|string|max:255',
'sku' => [
'nullable',
'string',
'max:100',
Rule::unique('products')->ignore($product->id),
],
'product_code' => 'nullable|string|max:100',
'product_category_id' => 'nullable|exists:product_categories,id',
'shelf_id' => 'nullable|exists:shelves,id',
'price' => 'required|numeric|min:0',
'sale_price' => 'nullable|numeric|min:0',
'stock_quantity' => 'required|integer|min:0',
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg,webp|max:4096',
'description' => 'nullable|string',
'is_active' => 'boolean',
'is_featured' => 'boolean',
]);
$imagePath = $product->image;
if ($request->hasFile('image')) {
// 旧画像の削除
if ($product->image && Storage::disk('public')->exists($product->image)) {
Storage::disk('public')->delete($product->image);
}
$imagePath = $request->file('image')->store('products', 'public');
}
$product->update([
'name' => $request->name,
'sku' => $request->sku,
'product_code' => $request->product_code,
'product_category_id' => $request->product_category_id,
'shelf_id' => $request->shelf_id,
'price' => $request->price,
'sale_price' => $request->sale_price,
'stock_quantity' => $request->stock_quantity ?? 0,
'image' => $imagePath,
'description' => $request->description,
'is_active' => $request->has('is_active'),
'is_featured' => $request->has('is_featured'),
]);
return redirect()->route('admin.products.index')
->with('flash_success', '商品情報を更新しました。');
}
/
* Remove the specified resource from storage.
*
* @param \App\Models\Product $product
* @return \Illuminate\Http\Response
*/
public function destroy(Product $product)
{
if ($product->image && Storage::disk('public')->exists($product->image)) {
Storage::disk('public')->delete($product->image);
}
$product->delete();
return redirect()->route('admin.products.index')
->with('flash_success', '商品を削除しました。');
}
}
backend/app/Models/DeliveryFeeCategory.php
e',
];
public function fees()
{
return $this->hasMany(DeliveryFee::class, 'category_id');
}
}
belongsTo(MangaTitle::class);
}
}
'decimal:2',
'tax_amount' => 'decimal:2',
'shipping_fee' => 'decimal:2',
'discount_amount' => 'decimal:2',
'total_amount' => 'decimal:2',
'shipped_at' => 'datetime',
'order_date' => 'datetime',
];
/
* Get the user that placed the order.
*/
public function user()
{
return $this->belongsTo(User::class);
}
/
* Get the items for the order.
*/
public function orderItems()
{
return $this->hasMany(OrderItem::class);
}
}
backend/app/Models/Product.php 'decimal:2',
'sale_price' => 'decimal:2',
'is_active' => 'boolean',
'is_featured' => 'boolean',
];
public function category()
{
return $this->belongsTo(ProductCategory::class, 'product_category_id');
}
public function shelf()
{
return $this->belongsTo(Shelf::class);
}
}
backend/database/migrations/2026_03_07_000002_create_contract_types_table.phpid();
$table->string('name')->comment('契約形態名 (例: 売切購入, レンタル, リース)');
$table->string('code')->unique()->comment('識別コード (例: purchase, rental, lease)');
$table->text('description')->nullable()->comment('説明・補足事項');
$table->boolean('requires_period')->default(false)->comment('契約期間の指定が必要か');
$table->integer('min_months')->nullable()->comment('最低契約月数');
$table->integer('sort_order')->default(0)->comment('表示順');
$table->boolean('is_active')->default(true)->comment('有効フラグ');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('contract_types');
}
};
backend/app/Models/MangaTitle.php 'boolean',
];
public function sets()
{
return $this->hasMany(MangaTitleSet::class);
}
public function contractPrices()
{
return $this->hasMany(MangaTitleContractPrice::class);
}
}
backend/database/migrations/2026_03_07_000006_create_contract_type_option_prices_table.phpid();
$table->foreignId('contract_type_id')->constrained()->onDelete('cascade');
$table->foreignId('option_id')->constrained()->onDelete('cascade');
$table->decimal('price', 10, 2)->default(0)->comment('一括基本料金');
$table->decimal('price_per_shelf', 10, 2)->default(0)->comment('1本(台)あたりの加算料金');
$table->enum('billing_type', ['one_time', 'monthly'])->default('one_time')->comment('請求種別 (初回のみ / 月額)');
$table->string('note')->nullable()->comment('備考');
$table->timestamps();
$table->unique(['contract_type_id', 'option_id'], 'cnt_opt_unique');
});
}
public function down(): void
{
Schema::dropIfExists('contract_type_option_prices');
}
};
backend/database/migrations/2026_03_07_000001_create_prefectures_table.phpid();
$table->string('name')->unique()->comment('都道府県名 (例: 東京都)');
$table->string('code', 10)->unique()->comment('コード (例: 13)');
$table->string('region_name')->nullable()->comment('地方名 (例: 関東)');
$table->integer('sort_order')->default(0)->comment('表示順');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('prefectures');
}
};
backend/app/Models/RegionPriceAdjustment.php 'decimal:2',
'base_price' => 'decimal:2',
'price_per_km' => 'decimal:2',
];
public function contractType()
{
return $this->belongsTo(ContractType::class);
}
public function option()
{
return $this->belongsTo(Option::class);
}
public function shelf()
{
return $this->belongsTo(Shelf::class);
}
public function sourcePrefecture()
{
return $this->belongsTo(Prefecture::class, 'source_prefecture_id');
}
}
backend/database/migrations/2026_03_07_000004_create_support_area_group_prefecture_table.phpid();
$table->foreignId('support_area_group_id')->constrained()->onDelete('cascade');
$table->foreignId('prefecture_id')->constrained()->onDelete('cascade');
$table->timestamps();
$table->unique(['support_area_group_id', 'prefecture_id'], 'group_pref_unique');
});
}
public function down(): void
{
Schema::dropIfExists('support_area_group_prefecture');
}
};
backend/app/Models/SupportAreaGroup.php 'boolean',
];
public function prefectures()
{
return $this->belongsToMany(Prefecture::class, 'support_area_group_prefecture');
}
public function deliveryFees()
{
return $this->hasMany(DeliveryFee::class, 'group_id');
}
}
backend/app/Models/Prefecture.php
is->hasMany(AreaDetailOptionPrice::class);
}
}
belongsToMany(SupportAreaGroup::class, 'support_area_group_prefecture');
}
public function areaDetailOptionPrices()
{
return $th8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
public function areaDetailOptionPrices()
{
return $thbelongsToMany(SupportAreaGroup::class, 'support_area_group_prefecture');
}
public function areaDetailOptionPrices()
{
return $this->hasMany(AreaDetailOptionPrice::class);
}
public function regionPriceAdjustments()
{
return $this->hasMany(RegionPriceAdjustment::class, 'source_prefecture_id');
}
}
backend/app/Models/User.php
)
{
return $this->hasMany(Order::class);
}
}
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'email',
'password',
'is_admin',
'company_name',
'phone',
'postal_code',
'prefecture',
'city',
'address1',
'address2',
];
/
* The attributes that should be hidden for serialization.
*
* @var array
*/
protected $hidden = [
'password',
'remember_token',
];
/
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
'is_admin' => 'boolean',
];
public function estimates()
{
return $this->hasMany(Estimate::class);
}
public function carts()
{
return $this->hasMany(Cart::class);
}
public function orders(backend/app/Models/MaintenanceLog.php 'datetime',
'cost' => 'decimal:2',
];
}
backend/app/Models/MangaTitleContractPrice.php
ct_type_id',
'shelf_count',
'base_price',
'monthly_price',
'notes',
];
protected $casts = [
'base_price' => 'decimal:2',
'monthly_price' => 'decimal:2',
];
public function mangaTitle()
{
return $this->belongsTo(MangaTitle::class);
}
public function contractType()
{
return $this->belongsTo(ContractType::class);
}
}
'decimal:2',
'is_active' => 'boolean',
];
public function contractTypeOptionPrices()
{
return $this->hasMany(ContractTypeOptionPrice::class);
}
public function areaDetailOptionPrices()
{
return $this->hasMany(AreaDetailOptionPrice::class);
}
public function regionPriceAdjustments()
{
return $this->hasMany(RegionPriceAdjustment::class);
}
}
backend/database/migrations/2026_03_07_000003_create_support_area_groups_table.phpid();
$table->string('name')->comment('グループ名 (例: 首都圏エリア, 関西エリア)');
$table->string('code')->unique()->comment('識別コード (例: shutoken, kansai)');
$table->text('description')->nullable()->comment('説明・補足');
$table->boolean('is_active')->default(true)->comment('有効フラグ');
$table->integer('sort_order')->default(0)->comment('表示順');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('support_area_groups');
}
};
backend/app/Models/ShelfContractPrice.php 'decimal:2',
'monthly_price' => 'decimal:2',
];
public function shelf()
{
return $this->belongsTo(Shelf::class);
}
public function contractType()
{
return $this->belongsTo(ContractType::class);
}
}
backend/app/Models/ProductCategory.php
'boolean',
];
public function parent()
{
return $this->belongsTo(ProductCategory::class, 'parent_id');
}
public function children()
{
return $this->hasMany(ProductCategory::class, 'parent_id')->orderBy('sort_order', 'asc');
}
public function products()
{
return $this->hasMany(Product::class);
}
}
ory;
use Illuminate\Database\Eloquent\Model;
class ProductCategory extends Model
{
use HasFactory;
protected $fillable = [
'name',
backend/database/migrations/2026_03_07_000005_create_options_table.php
id();
$table->string('name')->comment('オプション名 (例: 配送・設置作業, 帯掛け作業, オリジナルポップ作成)');
$table->string('code')->unique()->comment('識別コード (例: delivery_installation, obi, pop)');
$table->string('category')->default('general')->comment('カテゴリー (例: setup, maintenance, marketing)');
$table->text('description')->nullable()->comment('説明・詳細');
$table->decimal('default_price', 10, 2)->default(0)->comment('標準料金');
$table->enum('billing_type', ['one_time', 'monthly', 'per_shelf', 'custom'])->default('one_time')->comment('課金単位 (単発, 月額, 1台あたり, カスタム)');
$table->integer('sort_order')->default(0)->comment('表示順');
$table->boolean('is_active')->default(true)->comment('有効フラグ');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('options');
}
};
extends Migration
{
publicbackend/app/Models/Shelf.php
'decimal:2',
'is_active' => 'boolean',
];
public function contractPrices()
{
shelfTypeDeliveryFees()
{
return $this->hasMany(ShelfTypeDeliveryFee::class);
}
public function regionPriceAdjustments()
{
return $this->hasMany(RegionPriceAdjustment::class);
}
public function products()
{
return $this->hasMany(Product::class);
}
}
return $this->hasMany(ShelfContractPrice::class);
}
public functionbackend/app/Models/ShelfTypeDeliveryFee.php 'decimal:2',
'additional_shelf_fee' => 'decimal:2',
];
public function shelf()
{
return $this->belongsTo(Shelf::class);
}
public function areaGroup()
{
return $this->belongsTo(SupportAreaGroup::class, 'group_id');
}
}
backend/database/migrations/2026_03_07_000007_create_area_detail_option_prices_table.php
$table->string('city_name')->nullable()->comment('市区町村名 (特定地域のみの場合)');
$table->decimal('price', 10, 2)->default(0)->comment('エリア固有一括料金');
$table->decimal('price_per_shelf', 10, 2)->default(0)->comment('エリア固有1本あたり料金');
$table->string('price_type')->default('fixed')->comment('料金タイプ (fixed, distance_based等)');
$table->text('notes')->nullable()->comment('注記・条件説明');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('area_detail_option_prices');
}
};
id();
$table->foreignId('option_id')->constrained()->onDelete('cascade');
$table->foreignId('prefecture_id')->constrained()->onDelete('cascade');
backend/database/migrations/2026_03_07_000008_create_shelves_table.php
id();
$table->string('name')->comment('本棚の名称 (例: 5段ハイタイプ本棚, 3段ロータイプ本棚)');
$table->string('code')->unique()->comment('識別コード (例: high_5tier, low_3tier)');
$table->integer('capacity_books')->default(0)->comment('収納冊数目安 (例: 400)');
$table->integer('width_mm')->nullable()->comment('幅(mm)');
$table->integer('depth_mm')->nullable()->comment('奥行き(mm)');
$table->integer('height_mm')->nullable()->comment('高さ(mm)');
$table->decimal('price', 10, 2)->default(0)->comment('標準本体単価');
$table->text('description')->nullable()->comment('説明');
$table->boolean('is_active')->default(true)->comment('有効フラグ');
$table->integer('sort_order')->default(0)->comment('表示順');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('shelves');
}
};
backend/database/migrations/2026_03_07_000010_create_delivery_fee_categories_table.phpid();
$table->string('name')->comment('配送料カテゴリー名 (例: 通常配送, 重量物配送, 設営付き配送)');
$table->string('code')->unique()->comment('識別コード');
$table->text('description')->nullable()->comment('説明');
$table->integer('sort_order')->default(0)->comment('表示順');
$table->boolean('is_active')->default(true)->comment('有効フラグ');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('delivery_fee_categories');
}
};
backend/database/migrations/2026_03_07_000013_create_region_price_adjustments_table.phpid();
$table->foreignId('contract_type_id')->nullable()->constrained()->onDelete('cascade');
$table->foreignId('option_id')->nullable()->constrained()->onDelete('cascade');
$table->foreignId('shelf_id')->nullable()->constrained()->onDelete('cascade');
$table->foreignId('source_prefecture_id')->nullable()->constrained('prefectures')->onDelete('cascade')->comment('出発地/基準都道府県');
$table->string('target_province')->nullable()->comment('対象都道府県または地域');
$table->string('province_category')->nullable()->comment('地域区分 (例: 離島・山遠隔地, 特定都市部)');
$table->decimal('price_per_shelf', 10, 2)->default(0)->comment('1台あたり調整額');
$table->decimal('base_price', 10, 2)->default(0)->comment('一括基本調整額');
$table->decimal('price_per_km', 10, 2)->default(0)->comment('1kmあたり距離加算費');
$table->integer('min_distance_km')->nullable()->comment('適用最小距離(km)');
$table->integer('max_distance_km')->nullable()->comment('適用最大距離(km)');
$table->text('notes')->nullable()->comment('備考');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('region_price_adjustments');
}
};
backend/database/migrations/2026_03_07_000018_create_orders_table.phpid();
$table->string('order_number')->unique()->comment('注文番号 (例: ORD-20260307-XXXX)');
$table->foreignId('user_id')->nullable()->constrained()->onDelete('set null')->comment('購入ユーザーID');
$table->string('status')->default('pending')->comment('注文ステータス (pending, processing, shipped, completed, cancelled)');
$table->string('payment_status')->default('unpaid')->comment('決済ステータス (unpaid, paid, refunded, failed)');
$table->string('payment_method')->default('credit_card')->comment('決済手段 (credit_card, bank_transfer, invoice)');
$table->decimal('subtotal', 12, 2)->default(0)->comment('小計');
$table->decimal('tax_amount', 12, 2)->default(0)->comment('消費税額');
$table->decimal('shipping_fee', 12, 2)->default(0)->comment('送料');
$table->decimal('discount_amount', 12, 2)->default(0)->comment('値引き額');
$table->decimal('total_amount', 12, 2)->default(0)->comment('合計請求金額');
// お届け先情報
$table->string('shipping_name')->comment('配送先 お名前');
$table->string('shipping_kana')->nullable()->comment('配送先 フリガナ');
$table->string('shipping_postal_code', 10)->comment('配送先 郵便番号');
$table->string('shipping_prefecture', 50)->comment('配送先 都道府県');
$table->string('shipping_city')->comment('配送先 市区町村');
$table->string('shipping_address1')->comment('配送先 町名・番地');
$table->string('shipping_address2')->nullable()->comment('配送先 建物名・部屋番号');
$table->string('shipping_phone', 30)->comment('配送先 電話番号');
$table->string('shipping_email')->comment('配送先 メールアドレス');
// 配送情報
$table->string('tracking_number')->nullable()->comment('追跡番号');
$table->string('shipping_carrier')->nullable()->comment('配送業者 (ヤマト, 佐川, 福山等)');
$table->timestamp('shipped_at')->nullable()->comment('発送日時');
$table->text('admin_notes')->nullable()->comment('管理者メモ');
$table->text('customer_notes')->nullable()->comment('顧客備考');
$table->timestamp('order_date')->useCurrent()->comment('注文日時');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('orders');
}
};
backend/database/migrations/2026_03_07_000009_create_shelf_contract_prices_table.php
Schema::dropIfExists('shelf_contract_prices');
}
};
id();
$table->foreignId('shelf_id')->constrained()->onDelete('cascade');
$table->foreignId('contract_type_id')->constrained()->onDelete('cascade');
$table->decimal('price', 10, 2)->default(0)->comment('一括/購入単価');
$table->decimal('monthly_price', 10, 2)->default(0)->comment('月額単価');
$table->integer('min_months')->default(1)->comment('最低契約月数');
$table->string('note')->nullable()->comment('備考');
$table->timestamps();
$table->unique(['shelf_id', 'contract_type_id'], 'shelf_contract_unique');
});
}
public function down(): void
{
backend/database/seeders/MasterDataSeeder.php
'notes' => '1台目の送料代金'
]
);
}
// 9. Site Configuration Seeders
$siteConfigs = [
['key' => 'site_name', 'value' => 'Comic Shelf Simulation & Store'],
['key' => 'site_email', 'value' => 'info@comicshelf.jp'],
['key' => 'site_phone', 'value' => '03-1234-5678'],
['key' => 'site_postal_code', 'value' => '100-0005'],
['key' => 'site_address', 'value' => '東京都千代田区丸の内1-1-1'],
['key' => 'tax_rate', 'value' => '10'],
['key' => 'default_shipping_fee', 'value' => '1000'],
['key' => 'free_shipping_threshold', 'value' => '50000'],
['key' => 'meta_title', 'value' => '漫画本棚見積もり・注文シミュレーション'],
['key' => 'meta_keywords', 'value' => 'コミック本棚, 漫画棚, ネットカフェ本棚, レンタル本棚, 漫画全巻'],
['key' => 'meta_description', 'value' => 'コミック本棚の導入見積もりシミュレーションおよび関連商品のオンラインストアです。'],
];
foreach ($siteConfigs as $config) {
SiteConfig::updateOrCreate(['key' => $config['key']], ['value' => $config['value']]);
}
// 10. Sample Product Categories & Products
$cat1 = ProductCategory::updateOrCreate(
['slug' => 'manga-shelves'],
['name' => 'コミック専用本棚', 'description' => '各種サイズのコミック棚', 'sort_order' => 1, 'is_active' => true]
);
$cat2 = ProductCategory::updateOrCreate(
['slug' => 'accessories'],
['name' => '本棚アクセサリ・OP', 'description' => '仕切り板・ブックエンド・LED照明', 'sort_order' => 2, 'is_active' => true]
);
Product::updateOrCreate(
['sku' => 'PROD-SHELF-01'],
[
'name' => '【完成品】ハイタイプ大容量コミック本棚 900巾',
'product_code' => 'CS-900H',
'product_category_id' => $cat1->id,
'shelf_id' => $highShelf->id,
'price' => 28000,
'stock_quantity' => 50,
'description' => 'コミック約400冊をスッキリ収納可能な特注木製ラック。',
'is_active' => true,
'is_featured' => true,
]
);
Product::updateOrCreate(
['sku' => 'PROD-OPT-LED'],
[
'name' => '本棚用LEDテープライト (電球色/1.2m)',
'product_code' => 'ACC-LED-01',
'product_category_id' => $cat2->id,
'price' => 3500,
'stock_quantity' => 100,
'description' => '棚下を幻想的に照らす間接照明用LEDキット。',
'is_active' => true,
'is_featured' => false,
]
);
// 11. Sample Maintenance Log
MaintenanceLog::create([
'title' => '初期システムマスタデータ投入',
'category' => 'system_update',
'performed_at' => now(),
'performed_by' => 'システム管理者',
'details' => '全47都道府県、基本契約形態、オプション、本棚マスタ等の初期セットアップを実行しました。',
'cost' => 0,
]);
}
}
'admin@example.com'],
[
'name' => '管理者ユーザー',
'password' => Hash::make('password'),
'is_admin' => true,
'company_name' => '株式会社コミックラック',
'phone' => '03-1234-5678',
'postal_code' => '100-0005',
'prefecture' => '東京都',
'city' => '千代田区',
'address1' => '丸の内1-1-1',
]
);
// 2. Prefectures (47 Prefectures in Japan)
$prefecturesData = [
['code' => '01', 'name' => '北海道', 'region_name' => '北海道'],
['code' => '02', 'name' => '青森県', 'region_name' => '東北'],
['code' => '03', 'name' => '岩手県', 'region_name' => '東北'],
['code' => '04', 'name' => '宮城県', 'region_name' => '東北'],
['code' => '05', 'name' => '秋田県', 'region_name' => '東北'],
['code' => '06', 'name' => '山形県', 'region_name' => '東北'],
['code' => '07', 'name' => '福島県', 'region_name' => '東北'],
['code' => '08', 'name' => '茨城県', 'region_name' => '関東'],
['code' => '09', 'name' => '栃木県', 'region_name' => '関東'],
['code' => '10', 'name' => '群馬県', 'region_name' => '関東'],
['code' => '11', 'name' => '埼玉県', 'region_name' => '関東'],
['code' => '12', 'name' => '千葉県', 'region_name' => '関東'],
['code' => '13', 'name' => '東京都', 'region_name' => '関東'],
['code' => '14', 'name' => '神奈川県', 'region_name' => '関東'],
['code' => '15', 'name' => '新潟県', 'region_name' => '中部'],
['code' => '16', 'name' => '富山県', 'region_name' => '中部'],
['code' => '17', 'name' => '石川県', 'region_name' => '中部'],
['code' => '18', 'name' => '福井県', 'region_name' => '中部'],
['code' => '19', 'name' => '山梨県', 'region_name' => '中部'],
['code' => '20', 'name' => '長野県', 'region_name' => '中部'],
['code' => '21', 'name' => '岐阜県', 'region_name' => '中部'],
['code' => '22', 'name' => '静岡県', 'region_name' => '中部'],
['code' => '23', 'name' => '愛知県', 'region_name' => '中部'],
['code' => '24', 'name' => '三重県', 'region_name' => '近畿'],
['code' => '25', 'name' => '滋賀県', 'region_name' => '近畿'],
['code' => '26', 'name' => '京都府', 'region_name' => '近畿'],
['code' => '27', 'name' => '大阪府', 'region_name' => '近畿'],
['code' => '28', 'name' => '兵庫県', 'region_name' => '近畿'],
['code' => '29', 'name' => '奈良県', 'region_name' => '近畿'],
['code' => '30', 'name' => '和歌山県', 'region_name' => '近畿'],
['code' => '31', 'name' => '鳥取県', 'region_name' => '中国'],
['code' => '32', 'name' => '島根県', 'region_name' => '中国'],
['code' => '33', 'name' => '岡山県', 'region_name' => '中国'],
['code' => '34', 'name' => '広島県', 'region_name' => '中国'],
['code' => '35', 'name' => '山口県', 'region_name' => '中国'],
['code' => '36', 'name' => '徳島県', 'region_name' => '四国'],
['code' => '37', 'name' => '香川県', 'region_name' => '四国'],
['code' => '38', 'name' => '愛媛県', 'region_name' => '四国'],
['code' => '39', 'name' => '高知県', 'region_name' => '四国'],
['code' => '40', 'name' => '福岡県', 'region_name' => '九州'],
['code' => '41', 'name' => '佐賀県', 'region_name' => '九州'],
['code' => '42', 'name' => '長崎県', 'region_name' => '九州'],
['code' => '43', 'name' => '熊本県', 'region_name' => '九州'],
['code' => '44', 'name' => '大分県', 'region_name' => '九州'],
['code' => '45', 'name' => '宮崎県', 'region_name' => '九州'],
['code' => '46', 'name' => '鹿児島県', 'region_name' => '九州'],
['code' => '47', 'name' => '沖縄県', 'region_name' => '沖縄'],
];
foreach ($prefecturesData as $index => $pref) {
Prefecture::updateOrCreate(
['code' => $pref['code']],
[
'name' => $pref['name'],
'region_name' => $pref['region_name'],
'sort_order' => $index + 1,
]
);
}
// 3. Contract Types
$contractBuy = ContractType::updateOrCreate(
['code' => 'purchase'],
[
'name' => '売切購入',
'description' => '本棚とコミックを一括で購入して所有するプランです。',
'requires_period' => false,
'min_months' => null,
'sort_order' => 1,
'is_active' => true,
]
);
$contractRental = ContractType::updateOrCreate(
['code' => 'rental'],
[
'name' => 'レンタル',
'description' => '月額料金で手軽に導入できるレンタルプランです。',
'requires_period' => true,
'min_months' => 6,
'sort_order' => 2,
'is_active' => true,
]
);
$contractLease = ContractType::updateOrCreate(
['code' => 'lease'],
[
'name' => 'リース',
'description' => '長期利用向けの月額固定料金プランです(要審査)。',
'requires_period' => true,
'min_months' => 24,
'sort_order' => 3,
'is_active' => true,
]
);
// 4. Support Area Groups
$shutoken = SupportAreaGroup::updateOrCreate(
['code' => 'shutoken'],
[
'name' => '一都三県エリア(自社配送便)',
'description' => '東京都、神奈川県、埼玉県、千葉県',
'is_active' => true,
'sort_order' => 1,
]
);
$shutokenPrefIds = Prefecture::whereIn('name', ['東京都', '神奈川県', '埼玉県', '千葉県'])->pluck('id');
$shutoken->prefectures()->sync($shutokenPrefIds);
$otherJapan = SupportAreaGroup::updateOrCreate(
['code' => '全国エリア(路線便・宅配)'],
[
'name' => '全国主要エリア',
'description' => '一都三県を除く全国エリア',
'is_active' => true,
'sort_order' => 2,
]
);
$otherPrefIds = Prefecture::whereNotIn('name', ['東京都', '神奈川県', '埼玉県', '千葉県', '沖縄県'])->pluck('id');
$otherJapan->prefectures()->sync($otherPrefIds);
// 5. Options
$optDelivery = Option::updateOrCreate(
['code' => 'delivery_setup'],
[
'name' => '搬入・組み立て設置作業',
'category' => 'setup',
'description' => '専任スタッフが現地で本棚の組み立てと設置を行います。',
'default_price' => 15000,
'billing_type' => 'one_time',
'sort_order' => 1,
'is_active' => true,
]
);
$optObi = Option::updateOrCreate(
['code' => 'obi_wrapping'],
[
'name' => 'コミック帯掛け・ビニールカバー加工',
'category' => 'maintenance',
'description' => '全巻に傷・汚れ防止の透明保護カバーを取り付けます。',
'default_price' => 50,
'billing_type' => 'custom',
'sort_order' => 2,
'is_active' => true,
]
);
$optExchange = Option::updateOrCreate(
['code' => 'book_exchange'],
[
'name' => '定期コミック入れ替えサービス',
'category' => 'maintenance',
'description' => '3ヶ月ごとに人気最新作や要望作と一部入れ替えを行います。',
'default_price' => 3000,
'billing_type' => 'monthly',
'sort_order' => 3,
'is_active' => true,
]
);
// 6. Shelves
$highShelf = Shelf::updateOrCreate(
['code' => 'high_5tier'],
[
'name' => 'ハイタイプ5段コミックラック (幅90cm)',
'capacity_books' => 400,
'width_mm' => 900,
'depth_mm' => 300,
'height_mm' => 1800,
'price' => 25000,
'description' => '標準的なコミック本が約400冊収納できる高容量ラック。',
'is_active' => true,
'sort_order' => 1,
]
);
$lowShelf = Shelf::updateOrCreate(
['code' => 'low_3tier'],
[
'name' => 'ロータイプ3段コミックラック (幅90cm)',
'capacity_books' => 240,
'width_mm' => 900,
'depth_mm' => 300,
'height_mm' => 1100,
'price' => 15000,
'description' => '窓下やカウンター下に収まるコンパクトなラック。',
'is_active' => true,
'sort_order' => 2,
]
);
// 7. Shelf Contract Prices
ShelfContractPrice::updateOrCreate(
['shelf_id' => $highShelf->id, 'contract_type_id' => $contractBuy->id],
['price' => 25000, 'monthly_price' => 0, 'min_months' => 1]
);
ShelfContractPrice::updateOrCreate(
['shelf_id' => $highShelf->id, 'contract_type_id' => $contractRental->id],
['price' => 0, 'monthly_price' => 3500, 'min_months' => 6]
);
ShelfContractPrice::updateOrCreate(
['shelf_id' => $lowShelf->id, 'contract_type_id' => $contractBuy->id],
['price' => 15000, 'monthly_price' => 0, 'min_months' => 1]
);
ShelfContractPrice::updateOrCreate(
['shelf_id' => $lowShelf->id, 'contract_type_id' => $contractRental->id],
['price' => 0, 'monthly_price' => 2200, 'min_months' => 6]
);
// 8. Manga Titles & Sets & Contract Prices
$manga1 = MangaTitle::updateOrCreate(
['title' => '鬼滅の刃'],
[
'title_kana' => 'キメツノヤイバ',
'author' => '吾峠呼世晴',
'publisher' => '集英社',
'total_volumes' => 23,
'description' => '大ヒット和風ファンタジー冒険譚。全23巻完結。',
'is_active' => true,
]
);
MangaTitleSet::updateOrCreate(
['manga_title_id' => $manga1->id, 'min_volumes' => 1, 'max_volumes' => 23],
['shelf_count' => 1, 'notes' => '全23巻セット(1棚の半分程度使用)']
);
MangaTitleContractPrice::updateOrCreate(
['manga_title_id' => $manga1->id, 'contract_type_id' => $contractBuy->id, 'shelf_count' => 1],
['base_price' => 12000, 'monthly_price' => 0, 'notes' => '全巻買収コミック代金']
);
MangaTitleContractPrice::updateOrCreate(
['manga_title_id' => $manga1->id, 'contract_type_id' => $contractRental->id, 'shelf_count' => 1],
['base_price' => 0, 'monthly_price' => 1200, 'notes' => '月額コミックレンタル代金']
);
// Delivery Fee Sample
foreach ([$highShelf, $lowShelf] as $shelf) {
ShelfTypeDeliveryFee::updateOrCreate(
['shelf_id' => $shelf->id, 'group_id' => $shutoken->id],
[
'first_shelf_fee' => 5000,
'additional_shelf_fee' => 2000,
'notes' => '都内自社便価格'
]
);
ShelfTypeDeliveryFee::updateOrCreate(
['shelf_id' => $shelf->id, 'group_id' => $otherJapan->id],
[
'first_shelf_fee' => 8000,
'additional_shelf_fee' => 4000,
backend/resources/views/admin/contract_types/create.blade.php
>
最低契約月数