feat(order): implement order creation flow and pricing API
Introduce order management functionality including web routes for creating orders and a new API endpoint for price calculations. This change also cleans up the codebase by removing various debug scripts used during the development of the shipping rate logic. - Add `OrderController` to handle order creation and success redirection. - Add `POST /v1/calculate` endpoint for pricing calculations. - Define web routes for order creation flow (`/order`). - Remove multiple debug PHP scripts from the `04_Laravel` directory. - Add base layout and order view directories.
This commit is contained in:
parent
3aaa9a5481
commit
24fc56feaf
109
04_Laravel/app/Http/Controllers/OrderController.php
Normal file
109
04_Laravel/app/Http/Controllers/OrderController.php
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Enums\ShipmentDirection;
|
||||||
|
use App\Enums\ShipmentType;
|
||||||
|
use App\Models\Country;
|
||||||
|
use App\Models\Shipment;
|
||||||
|
use App\Models\ShipmentItem;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
|
class OrderController extends Controller
|
||||||
|
{
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
$countries = Country::orderBy('name')->get();
|
||||||
|
$directions = ShipmentDirection::cases();
|
||||||
|
$types = ShipmentType::cases();
|
||||||
|
|
||||||
|
return view('orders.create', compact('countries', 'directions', 'types'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$validator = Validator::make($request->all(), [
|
||||||
|
'direction' => ['required', 'in:export,import'],
|
||||||
|
'type' => ['required', 'in:DOC_NORMAL,DOC_ECONOMY,PARCEL'],
|
||||||
|
'from_country_id' => ['required', 'exists:countries,id'],
|
||||||
|
'to_country_id' => ['required', 'exists:countries,id'],
|
||||||
|
'forwarder' => ['nullable', 'string', 'max:255'],
|
||||||
|
'weight' => ['required', 'numeric', 'min:0.1'],
|
||||||
|
'volumetric_weight' => ['nullable', 'numeric', 'min:0'],
|
||||||
|
'dimensions' => ['nullable', 'string', 'max:255'],
|
||||||
|
'sender_name' => ['required', 'string', 'max:255'],
|
||||||
|
'sender_company' => ['nullable', 'string', 'max:255'],
|
||||||
|
'sender_phone' => ['required', 'string', 'max:50'],
|
||||||
|
'sender_email' => ['nullable', 'email', 'max:255'],
|
||||||
|
'sender_address' => ['required', 'string', 'max:500'],
|
||||||
|
'sender_city' => ['required', 'string', 'max:255'],
|
||||||
|
'sender_zip' => ['nullable', 'string', 'max:20'],
|
||||||
|
'sender_id_number' => ['nullable', 'string', 'max:50'],
|
||||||
|
'receiver_name' => ['required', 'string', 'max:255'],
|
||||||
|
'receiver_company' => ['nullable', 'string', 'max:255'],
|
||||||
|
'receiver_phone' => ['required', 'string', 'max:50'],
|
||||||
|
'receiver_email' => ['nullable', 'email', 'max:255'],
|
||||||
|
'receiver_address' => ['required', 'string', 'max:500'],
|
||||||
|
'receiver_city' => ['required', 'string', 'max:255'],
|
||||||
|
'receiver_zip' => ['nullable', 'string', 'max:20'],
|
||||||
|
'receiver_id_number' => ['nullable', 'string', 'max:50'],
|
||||||
|
'items' => ['required', 'array', 'min:1', 'max:9'],
|
||||||
|
'items.*.description' => ['required', 'string', 'max:500'],
|
||||||
|
'items.*.hs_code' => ['required', 'string', 'max:50'],
|
||||||
|
'items.*.quantity' => ['required', 'numeric', 'min:1'],
|
||||||
|
'items.*.unit_price' => ['required', 'numeric', 'min:0'],
|
||||||
|
'items.*.total_usd' => ['required', 'numeric', 'min:0'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$data = $validator->validated();
|
||||||
|
|
||||||
|
$shipment = Shipment::create([
|
||||||
|
'awb_no' => 'IFNEX-' . strtoupper(uniqid()),
|
||||||
|
'direction' => $data['direction'],
|
||||||
|
'type' => $data['type'],
|
||||||
|
'from_country_id' => $data['from_country_id'],
|
||||||
|
'to_country_id' => $data['to_country_id'],
|
||||||
|
'forwarder' => $data['forwarder'] ?? null,
|
||||||
|
'weight' => $data['weight'],
|
||||||
|
'volumetric_weight' => $data['volumetric_weight'] ?? $data['weight'],
|
||||||
|
'chargeable_weight' => max($data['weight'], $data['volumetric_weight'] ?? $data['weight']),
|
||||||
|
'dimensions' => $data['dimensions'] ?? null,
|
||||||
|
'sender_name' => $data['sender_name'],
|
||||||
|
'sender_company' => $data['sender_company'] ?? null,
|
||||||
|
'sender_phone' => $data['sender_phone'],
|
||||||
|
'sender_email' => $data['sender_email'] ?? null,
|
||||||
|
'sender_address' => $data['sender_address'],
|
||||||
|
'sender_city' => $data['sender_city'],
|
||||||
|
'sender_zip' => $data['sender_zip'] ?? null,
|
||||||
|
'sender_id_number' => $data['sender_id_number'] ?? null,
|
||||||
|
'receiver_name' => $data['receiver_name'],
|
||||||
|
'receiver_company' => $data['receiver_company'] ?? null,
|
||||||
|
'receiver_phone' => $data['receiver_phone'],
|
||||||
|
'receiver_email' => $data['receiver_email'] ?? null,
|
||||||
|
'receiver_address' => $data['receiver_address'],
|
||||||
|
'receiver_city' => $data['receiver_city'],
|
||||||
|
'receiver_zip' => $data['receiver_zip'] ?? null,
|
||||||
|
'receiver_id_number' => $data['receiver_id_number'] ?? null,
|
||||||
|
'status' => 'processed',
|
||||||
|
]);
|
||||||
|
|
||||||
|
foreach ($data['items'] as $index => $item) {
|
||||||
|
$shipment->items()->create([
|
||||||
|
'row_number' => $index + 1,
|
||||||
|
'description' => $item['description'],
|
||||||
|
'hs_code' => $item['hs_code'],
|
||||||
|
'quantity' => $item['quantity'],
|
||||||
|
'unit_price' => $item['unit_price'],
|
||||||
|
'total_usd' => $item['total_usd'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('orders.success', $shipment)->with('success', 'سفارش شما با موفقیت ثبت شد.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function success(Shipment $shipment)
|
||||||
|
{
|
||||||
|
return view('orders.success', compact('shipment'));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,27 +0,0 @@
|
|||||||
<?php
|
|
||||||
require 'vendor/autoload.php';
|
|
||||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
|
||||||
|
|
||||||
$file = 'storage/app/public/01KYWGVNKS5TNMN37RV77PCYNZ.xlsx';
|
|
||||||
$spreadsheet = IOFactory::load($file);
|
|
||||||
|
|
||||||
$sheet = $spreadsheet->getSheetByName('Assumptions');
|
|
||||||
echo "=== Assumptions sheet ===" . PHP_EOL;
|
|
||||||
echo "Total rows: " . $sheet->getHighestRow() . PHP_EOL;
|
|
||||||
echo "Total cols: " . $sheet->getHighestColumn() . PHP_EOL;
|
|
||||||
|
|
||||||
for ($row = 1; $row <= $sheet->getHighestRow(); $row++) {
|
|
||||||
$rowData = $sheet->rangeToArray("A{$row}:Z{$row}")[0];
|
|
||||||
echo "Row $row: " . implode(' | ', array_map(function($cell) {
|
|
||||||
return $cell === null ? '(null)' : $cell;
|
|
||||||
}, $rowData)) . PHP_EOL;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo PHP_EOL . "=== Zone sheet ===" . PHP_EOL;
|
|
||||||
$sheet = $spreadsheet->getSheetByName('Zone');
|
|
||||||
for ($row = 1; $row <= $sheet->getHighestRow(); $row++) {
|
|
||||||
$rowData = $sheet->rangeToArray("A{$row}:Z{$row}")[0];
|
|
||||||
echo "Row $row: " . implode(' | ', array_map(function($cell) {
|
|
||||||
return $cell === null ? '(null)' : $cell;
|
|
||||||
}, $rowData)) . PHP_EOL;
|
|
||||||
}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
<?php
|
|
||||||
require 'vendor/autoload.php';
|
|
||||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
|
||||||
|
|
||||||
$file = 'storage/app/public/01KYWGVNKS5TNMN37RV77PCYNZ.xlsx';
|
|
||||||
$ss = IOFactory::load($file);
|
|
||||||
$sheet = $ss->getSheetByName('DocNor');
|
|
||||||
|
|
||||||
echo "=== Raw Excel values (DocNor) ===" . PHP_EOL;
|
|
||||||
for ($r = 9; $r <= 11; $r++) {
|
|
||||||
$row = $sheet->rangeToArray("A{$r}:C{$r}")[0];
|
|
||||||
echo "Row {$r}: weight={$row[0]}, zone={$row[1]}, raw={$row[2]}, /455000=" . ($row[2] / 455000) . PHP_EOL;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo PHP_EOL . "=== Stored values (DB) ===" . PHP_EOL;
|
|
||||||
$records = \App\Models\ShippingRate::where('direction', 'export')
|
|
||||||
->where('type', 'DOC_NORMAL')
|
|
||||||
->where('weight', 1)
|
|
||||||
->first(['zone_1', 'zone_2', 'zone_3', 'zone_4', 'zone_5', 'zone_6', 'zone_7']);
|
|
||||||
echo json_encode($records, JSON_PRETTY_PRINT) . PHP_EOL;
|
|
||||||
|
|
||||||
echo PHP_EOL . "=== Raw Excel values (Export Rate) ===" . PHP_EOL;
|
|
||||||
$sheet2 = $ss->getSheetByName('Export Rate');
|
|
||||||
for ($r = 6; $r <= 9; $r++) {
|
|
||||||
$row = $sheet2->rangeToArray("A{$r}:J{$r}")[0];
|
|
||||||
echo "Row {$r}: " . implode(' | ', array_map(function($c) { return $c === null ? '(null)' : $c; }, $row)) . PHP_EOL;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo PHP_EOL . "=== System Settings ===" . PHP_EOL;
|
|
||||||
echo "aed_to_irr: " . \App\Models\SystemSetting::get('aed_to_irr', 455000) . PHP_EOL;
|
|
||||||
echo "profit_margin: " . \App\Models\SystemSetting::get('profit_margin', 1.25) . PHP_EOL;
|
|
||||||
echo "vat_rate: " . \App\Models\SystemSetting::get('vat_rate', 0.09) . PHP_EOL;
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
<?php
|
|
||||||
require 'vendor/autoload.php';
|
|
||||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
|
||||||
|
|
||||||
$file = 'storage/app/public/01KYWGVNKS5TNMN37RV77PCYNZ.xlsx';
|
|
||||||
$spreadsheet = IOFactory::load($file);
|
|
||||||
$sheet = $spreadsheet->getSheetByName('DocEco');
|
|
||||||
|
|
||||||
echo "=== DocEco: looking for weight 2, zone 3 ===" . PHP_EOL;
|
|
||||||
for ($row = 1; $row <= $sheet->getHighestRow(); $row++) {
|
|
||||||
$rowData = $sheet->rangeToArray("A{$row}:C{$row}")[0];
|
|
||||||
if ((float) $rowData[0] == 2 && (int) $rowData[1] == 3) {
|
|
||||||
echo "Row $row: Weight={$rowData[0]}, Zone={$rowData[1]}, Value={$rowData[2]}" . PHP_EOL;
|
|
||||||
echo "Divided by 455000: " . ($rowData[2] / 455000) . PHP_EOL;
|
|
||||||
echo "Divided by 37000: " . ($rowData[2] / 37000) . PHP_EOL;
|
|
||||||
echo "Divided by 30000: " . ($rowData[2] / 30000) . PHP_EOL;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
echo PHP_EOL . "=== DocEco: all rows ===" . PHP_EOL;
|
|
||||||
for ($row = 2; $row <= $sheet->getHighestRow(); $row++) {
|
|
||||||
$rowData = $sheet->rangeToArray("A{$row}:C{$row}")[0];
|
|
||||||
echo "Row $row: {$rowData[0]} | {$rowData[1]} | {$rowData[2]}" . PHP_EOL;
|
|
||||||
}
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
<?php
|
|
||||||
require 'vendor/autoload.php';
|
|
||||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
|
||||||
|
|
||||||
$file = 'storage/app/public/01KYWGVNKS5TNMN37RV77PCYNZ.xlsx';
|
|
||||||
$spreadsheet = IOFactory::load($file);
|
|
||||||
|
|
||||||
// Check all sheet names
|
|
||||||
echo "Sheet names:" . PHP_EOL;
|
|
||||||
foreach ($spreadsheet->getSheetNames() as $name) {
|
|
||||||
echo " - $name" . PHP_EOL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check DocNor for any notes
|
|
||||||
$sheet = $spreadsheet->getSheetByName('DocNor');
|
|
||||||
echo PHP_EOL . "=== DocNor notes/comments ===" . PHP_EOL;
|
|
||||||
$comments = $sheet->getComments();
|
|
||||||
foreach ($comments as $cell => $comment) {
|
|
||||||
echo "Cell $cell: " . $comment->getText() . PHP_EOL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for any cells with formulas
|
|
||||||
echo PHP_EOL . "=== DocNor formulas ===" . PHP_EOL;
|
|
||||||
for ($row = 1; $row <= 5; $row++) {
|
|
||||||
for ($col = 1; $col <= 3; $col++) {
|
|
||||||
$cell = $sheet->getCellByColumnAndRow($col, $row);
|
|
||||||
if ($cell->getDataType() == \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_FORMULA) {
|
|
||||||
echo "Cell " . $cell->getCoordinate() . ": " . $cell->getValue() . " = " . $cell->getCalculatedValue() . PHP_EOL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if there's any metadata
|
|
||||||
echo PHP_EOL . "=== DocNor first cell values ===" . PHP_EOL;
|
|
||||||
echo "A1: " . $sheet->getCell('A1')->getValue() . PHP_EOL;
|
|
||||||
echo "B1: " . $sheet->getCell('B1')->getValue() . PHP_EOL;
|
|
||||||
echo "C1: " . $sheet->getCell('C1')->getValue() . PHP_EOL;
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
<?php
|
|
||||||
require 'vendor/autoload.php';
|
|
||||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
|
||||||
|
|
||||||
$file = 'storage/app/public/01KYWGVNKS5TNMN37RV77PCYNZ.xlsx';
|
|
||||||
$spreadsheet = IOFactory::load($file);
|
|
||||||
|
|
||||||
$sheets = ['DocNor', 'DocEco', 'Parcel', 'Import Rate', 'Export Rate'];
|
|
||||||
|
|
||||||
foreach ($sheets as $sheetName) {
|
|
||||||
$sheet = $spreadsheet->getSheetByName($sheetName);
|
|
||||||
if (!$sheet) {
|
|
||||||
echo "Sheet '$sheetName' not found" . PHP_EOL;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "=== Sheet: $sheetName ===" . PHP_EOL;
|
|
||||||
echo "Total rows: " . $sheet->getHighestRow() . PHP_EOL;
|
|
||||||
echo "Total cols: " . $sheet->getHighestColumn() . PHP_EOL;
|
|
||||||
|
|
||||||
// Print first 10 rows
|
|
||||||
for ($row = 1; $row <= min(10, $sheet->getHighestRow()); $row++) {
|
|
||||||
$rowData = $sheet->rangeToArray("A{$row}:J{$row}")[0];
|
|
||||||
echo "Row $row: " . implode(' | ', array_map(function($cell) {
|
|
||||||
return $cell === null ? '(null)' : $cell;
|
|
||||||
}, $rowData)) . PHP_EOL;
|
|
||||||
}
|
|
||||||
echo PHP_EOL;
|
|
||||||
}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
<?php
|
|
||||||
require 'vendor/autoload.php';
|
|
||||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
|
||||||
|
|
||||||
$file = 'storage/app/public/01KYWGVNKS5TNMN37RV77PCYNZ.xlsx';
|
|
||||||
$spreadsheet = IOFactory::load($file);
|
|
||||||
|
|
||||||
// Search all sheets for exchange rate or currency info
|
|
||||||
foreach ($spreadsheet->getSheetNames() as $sheetName) {
|
|
||||||
$sheet = $spreadsheet->getSheetByName($sheetName);
|
|
||||||
echo "=== $sheetName ===" . PHP_EOL;
|
|
||||||
|
|
||||||
foreach ($sheet->getCellCollection() as $cell) {
|
|
||||||
$value = $sheet->getCell($cell)->getValue();
|
|
||||||
if (is_string($value) && (stripos($value, 'rate') !== false || stripos($value, 'exchange') !== false || stripos($value, 'currency') !== false || stripos($value, 'aed') !== false || stripos($value, 'irr') !== false || stripos($value, 'rial') !== false || stripos($value, 'dirham') !== false)) {
|
|
||||||
echo " Cell {$cell}: {$value}" . PHP_EOL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
<?php
|
|
||||||
require 'vendor/autoload.php';
|
|
||||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
|
||||||
|
|
||||||
$file = 'storage/app/public/01KYWGVNKS5TNMN37RV77PCYNZ.xlsx';
|
|
||||||
$spreadsheet = IOFactory::load($file);
|
|
||||||
|
|
||||||
foreach (['Start', 'Form', 'DATES'] as $sheetName) {
|
|
||||||
$sheet = $spreadsheet->getSheetByName($sheetName);
|
|
||||||
if (!$sheet) continue;
|
|
||||||
echo "=== $sheetName ===" . PHP_EOL;
|
|
||||||
for ($row = 1; $row <= min(10, $sheet->getHighestRow()); $row++) {
|
|
||||||
$rowData = $sheet->rangeToArray("A{$row}:Z{$row}")[0];
|
|
||||||
echo "Row $row: " . implode(' | ', array_map(function($cell) {
|
|
||||||
return $cell === null ? '(null)' : $cell;
|
|
||||||
}, $rowData)) . PHP_EOL;
|
|
||||||
}
|
|
||||||
echo PHP_EOL;
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
<?php
|
|
||||||
require 'vendor/autoload.php';
|
|
||||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
|
||||||
|
|
||||||
$file = 'storage/app/public/01KYWGVNKS5TNMN37RV77PCYNZ.xlsx';
|
|
||||||
$spreadsheet = IOFactory::load($file);
|
|
||||||
|
|
||||||
// Check Export Rate sheet more thoroughly
|
|
||||||
$sheet = $spreadsheet->getSheetByName('Export Rate');
|
|
||||||
echo "=== Export Rate full structure ===" . PHP_EOL;
|
|
||||||
for ($row = 1; $row <= 15; $row++) {
|
|
||||||
$rowData = $sheet->rangeToArray("A{$row}:L{$row}")[0];
|
|
||||||
echo "Row $row: " . implode(' | ', array_map(function($cell) {
|
|
||||||
return $cell === null ? '(null)' : $cell;
|
|
||||||
}, $rowData)) . PHP_EOL;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo PHP_EOL . "=== Import Rate full structure ===" . PHP_EOL;
|
|
||||||
$sheet = $spreadsheet->getSheetByName('Import Rate');
|
|
||||||
for ($row = 1; $row <= 15; $row++) {
|
|
||||||
$rowData = $sheet->rangeToArray("A{$row}:K{$row}")[0];
|
|
||||||
echo "Row $row: " . implode(' | ', array_map(function($cell) {
|
|
||||||
return $cell === null ? '(null)' : $cell;
|
|
||||||
}, $rowData)) . PHP_EOL;
|
|
||||||
}
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
<?php
|
|
||||||
require 'vendor/autoload.php';
|
|
||||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
|
||||||
|
|
||||||
$file = 'storage/app/public/01KYWGVNKS5TNMN37RV77PCYNZ.xlsx';
|
|
||||||
$ss = IOFactory::load($file);
|
|
||||||
|
|
||||||
echo "=== Export Rate structure ===" . PHP_EOL;
|
|
||||||
$sheet = $ss->getSheetByName('Export Rate');
|
|
||||||
for ($r = 4; $r <= 14; $r++) {
|
|
||||||
$row = $sheet->rangeToArray("A{$r}:L{$r}")[0];
|
|
||||||
echo "Row {$r}: " . implode(' | ', array_map(function($c) { return $c === null ? '(null)' : $c; }, $row)) . PHP_EOL;
|
|
||||||
}
|
|
||||||
|
|
||||||
echo PHP_EOL . "=== Import Rate structure ===" . PHP_EOL;
|
|
||||||
$sheet = $ss->getSheetByName('Import Rate');
|
|
||||||
for ($r = 4; $r <= 14; $r++) {
|
|
||||||
$row = $sheet->rangeToArray("A{$r}:K{$r}")[0];
|
|
||||||
echo "Row {$r}: " . implode(' | ', array_map(function($c) { return $c === null ? '(null)' : $c; }, $row)) . PHP_EOL;
|
|
||||||
}
|
|
||||||
55
04_Laravel/resources/views/layouts/app.blade.php
Normal file
55
04_Laravel/resources/views/layouts/app.blade.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" dir="rtl">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>{{ config('app.name', 'IFNEX Logistics') }} — ثبت سفارش</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body class="bg-gray-50 text-gray-800 font-sans">
|
||||||
|
<nav class="bg-white shadow-sm border-b border-gray-200">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="flex justify-between h-16">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<a href="/" class="text-xl font-bold text-orange-600">IFNEX Logistics</a>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<a href="{{ route('orders.create') }}" class="text-gray-600 hover:text-orange-600 px-3 py-2 rounded-md text-sm font-medium">ثبت سفارش</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="py-8">
|
||||||
|
@if(session('success'))
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mb-6">
|
||||||
|
<div class="bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded-md">
|
||||||
|
{{ session('success') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if($errors->any())
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 mb-6">
|
||||||
|
<div class="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-md">
|
||||||
|
<ul class="list-disc list-inside">
|
||||||
|
@foreach($errors->all() as $error)
|
||||||
|
<li>{{ $error }}</li>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@yield('content')
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="bg-white border-t border-gray-200 mt-auto">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||||
|
<p class="text-center text-sm text-gray-500">© {{ date('Y') }} IFNEX Logistics. All rights reserved.</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
@stack('scripts')
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
405
04_Laravel/resources/views/orders/create.blade.php
Normal file
405
04_Laravel/resources/views/orders/create.blade.php
Normal file
@ -0,0 +1,405 @@
|
|||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<div class="bg-white shadow rounded-lg">
|
||||||
|
<div class="px-6 py-5 border-b border-gray-200">
|
||||||
|
<h1 class="text-2xl font-bold text-gray-900">فرم ثبت سفارش</h1>
|
||||||
|
<p class="mt-1 text-sm text-gray-500">لطفاً اطلاعات مرسوله و کالاهای گمرکی را وارد کنید</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="{{ route('orders.store') }}" method="POST" id="orderForm" class="p-6 space-y-8">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<input type="hidden" name="direction" id="direction" value="export">
|
||||||
|
<input type="hidden" name="type" id="type" value="DOC_NORMAL">
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||||
|
<div class="space-y-6">
|
||||||
|
<h2 class="text-lg font-semibold text-gray-900 border-b pb-2">اطلاعات مسیر</h2>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">نوع سرویس</label>
|
||||||
|
<select id="serviceType" class="w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500">
|
||||||
|
<option value="DOC_NORMAL" selected>Document Normal</option>
|
||||||
|
<option value="DOC_ECONOMY">Document Economy</option>
|
||||||
|
<option value="PARCEL">Parcel</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">جهت ارسال</label>
|
||||||
|
<select id="directionSelect" class="w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500">
|
||||||
|
<option value="export" selected>صادرات (از ایران)</option>
|
||||||
|
<option value="import">واردات (به ایران)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">کشور مبدأ</label>
|
||||||
|
<select name="from_country_id" id="fromCountry" class="w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500" required>
|
||||||
|
<option value="">انتخاب کنید</option>
|
||||||
|
@foreach($countries as $country)
|
||||||
|
<option value="{{ $country->id }}" data-iso="{{ $country->iso_code }}" {{ old('from_country_id') == $country->id ? 'selected' : '' }}>{{ $country->name }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">کشور مقصد</label>
|
||||||
|
<select name="to_country_id" id="toCountry" class="w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500" required>
|
||||||
|
<option value="">انتخاب کنید</option>
|
||||||
|
@foreach($countries as $country)
|
||||||
|
<option value="{{ $country->id }}" data-iso="{{ $country->iso_code }}" {{ old('to_country_id') == $country->id ? 'selected' : '' }}>{{ $country->name }}</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">فراوئر</label>
|
||||||
|
<input type="text" name="forwarder" value="{{ old('forwarder') }}" class="w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500" placeholder="مثال: DHL, FedEx">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">وزن واقعی (kg)</label>
|
||||||
|
<input type="number" step="0.01" name="weight" id="weight" value="{{ old('weight') }}" class="w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">وزن حجمی (kg)</label>
|
||||||
|
<input type="number" step="0.01" name="volumetric_weight" id="volumetricWeight" value="{{ old('volumetric_weight') }}" class="w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">ابعاد (طول × عرض × ارتفاع به cm)</label>
|
||||||
|
<input type="text" name="dimensions" value="{{ old('dimensions') }}" class="w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500" placeholder="مثال: 30×20×10">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="spotRateWarning" class="hidden bg-yellow-50 border border-yellow-200 rounded-md p-4">
|
||||||
|
<p class="text-sm text-yellow-700">مرسولات بالای ۳۰ کیلوگرم نیاز به استعلام قیمت ویژه دارند. کارشناسان ما با شما تماس میگیرند.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-6">
|
||||||
|
<h2 class="text-lg font-semibold text-gray-900 border-b pb-2">اطلاعات فرستنده</h2>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">نام <span class="text-red-500">*</span></label>
|
||||||
|
<input type="text" name="sender_name" value="{{ old('sender_name') }}" class="w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">شرکت</label>
|
||||||
|
<input type="text" name="sender_company" value="{{ old('sender_company') }}" class="w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">تلفن <span class="text-red-500">*</span></label>
|
||||||
|
<input type="text" name="sender_phone" value="{{ old('sender_phone') }}" class="w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">ایمیل</label>
|
||||||
|
<input type="email" name="sender_email" value="{{ old('sender_email') }}" class="w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">آدرس <span class="text-red-500">*</span></label>
|
||||||
|
<input type="text" name="sender_address" value="{{ old('sender_address') }}" class="w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-3 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">شهر <span class="text-red-500">*</span></label>
|
||||||
|
<input type="text" name="sender_city" value="{{ old('sender_city') }}" class="w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">کد پستی</label>
|
||||||
|
<input type="text" name="sender_zip" value="{{ old('sender_zip') }}" class="w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">شماره شناسنامه</label>
|
||||||
|
<input type="text" name="sender_id_number" value="{{ old('sender_id_number') }}" class="w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="border-t border-gray-200 pt-8">
|
||||||
|
<h2 class="text-lg font-semibold text-gray-900 border-b pb-2 mb-6">اطلاعات گیرنده</h2>
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">نام <span class="text-red-500">*</span></label>
|
||||||
|
<input type="text" name="receiver_name" value="{{ old('receiver_name') }}" class="w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">شرکت</label>
|
||||||
|
<input type="text" name="receiver_company" value="{{ old('receiver_company') }}" class="w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">تلفن <span class="text-red-500">*</span></label>
|
||||||
|
<input type="text" name="receiver_phone" value="{{ old('receiver_phone') }}" class="w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">ایمیل</label>
|
||||||
|
<input type="email" name="receiver_email" value="{{ old('receiver_email') }}" class="w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">آدرس <span class="text-red-500">*</span></label>
|
||||||
|
<input type="text" name="receiver_address" value="{{ old('receiver_address') }}" class="w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-3 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">شهر <span class="text-red-500">*</span></label>
|
||||||
|
<input type="text" name="receiver_city" value="{{ old('receiver_city') }}" class="w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500" required>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">کد پستی</label>
|
||||||
|
<input type="text" name="receiver_zip" value="{{ old('receiver_zip') }}" class="w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">شماره شناسنامه</label>
|
||||||
|
<input type="text" name="receiver_id_number" value="{{ old('receiver_id_number') }}" class="w-full rounded-md border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="border-t border-gray-200 pt-8">
|
||||||
|
<div class="flex justify-between items-center mb-6">
|
||||||
|
<h2 class="text-lg font-semibold text-gray-900 border-b pb-2">کالاهای گمرکی</h2>
|
||||||
|
<button type="button" id="addItemBtn" class="inline-flex items-center px-4 py-2 bg-orange-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-orange-700 focus:bg-orange-700 active:bg-orange-900 focus:outline-none focus:ring-2 focus:ring-orange-500 focus:ring-offset-2 transition ease-in-out duration-150">
|
||||||
|
+ افزودن ردیف
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="overflow-x-auto">
|
||||||
|
<table class="min-w-full divide-y divide-gray-200 border border-gray-200 rounded-lg overflow-hidden">
|
||||||
|
<thead class="bg-gray-50">
|
||||||
|
<tr>
|
||||||
|
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase w-16">ردیف</th>
|
||||||
|
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase">شرح کالا</th>
|
||||||
|
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase w-32">کد HS</th>
|
||||||
|
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase w-24">تعداد</th>
|
||||||
|
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase w-32">قیمت واحد (USD)</th>
|
||||||
|
<th class="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase w-36">جمع (USD)</th>
|
||||||
|
<th class="px-4 py-3 text-center text-xs font-medium text-gray-500 uppercase w-20">عملیات</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="itemsTableBody" class="bg-white divide-y divide-gray-200">
|
||||||
|
<tr class="item-row">
|
||||||
|
<td class="px-4 py-3"><input type="text" value="1" readonly class="w-full rounded border-gray-300 bg-gray-100 text-center text-sm"></td>
|
||||||
|
<td class="px-4 py-3"><input type="text" name="items[0][description]" class="w-full rounded border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500 text-sm" placeholder="شرح کالا" required></td>
|
||||||
|
<td class="px-4 py-3"><input type="text" name="items[0][hs_code]" class="w-full rounded border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500 text-sm" placeholder="HS Code" required></td>
|
||||||
|
<td class="px-4 py-3"><input type="number" name="items[0][quantity]" class="w-full rounded border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500 text-sm qty-input" placeholder="1" min="1" required></td>
|
||||||
|
<td class="px-4 py-3"><input type="number" step="0.01" name="items[0][unit_price]" class="w-full rounded border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500 text-sm price-input" placeholder="0.00" min="0" required></td>
|
||||||
|
<td class="px-4 py-3"><input type="number" step="0.01" name="items[0][total_usd]" class="w-full rounded border-gray-300 bg-gray-50 text-left text-sm total-input" placeholder="0.00" readonly></td>
|
||||||
|
<td class="px-4 py-3 text-center"></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 flex justify-end">
|
||||||
|
<div class="bg-gray-50 rounded-lg p-4 border border-gray-200">
|
||||||
|
<div class="text-sm text-gray-600">جمع کل فاکتور (USD):</div>
|
||||||
|
<div class="text-2xl font-bold text-gray-900" id="invoiceTotal">0.00</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="priceResult" class="hidden border-t border-gray-200 pt-8">
|
||||||
|
<h2 class="text-lg font-semibold text-gray-900 border-b pb-2 mb-6">قیمت محاسبه شده</h2>
|
||||||
|
<div class="bg-orange-50 border border-orange-200 rounded-lg p-6">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
|
<div>
|
||||||
|
<div class="text-sm text-gray-600">قیمت پایه</div>
|
||||||
|
<div class="text-lg font-semibold" id="basePrice">—</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-sm text-gray-600">قیمت نهایی (ریال)</div>
|
||||||
|
<div class="text-lg font-semibold text-orange-600" id="totalFee">—</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-sm text-gray-600">زون</div>
|
||||||
|
<div class="text-lg font-semibold" id="zone">—</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="border-t border-gray-200 pt-6 flex justify-end">
|
||||||
|
<button type="submit" id="submitBtn" class="inline-flex items-center px-6 py-3 bg-orange-600 border border-transparent rounded-md font-semibold text-white hover:bg-orange-700 focus:bg-orange-700 active:bg-orange-900 focus:outline-none focus:ring-2 focus:ring-orange-500 focus:ring-offset-2 transition ease-in-out duration-150">
|
||||||
|
ثبت سفارش
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@push('scripts')
|
||||||
|
<script>
|
||||||
|
let itemCount = 1;
|
||||||
|
const maxItems = 9;
|
||||||
|
|
||||||
|
function updateRowNumbers() {
|
||||||
|
document.querySelectorAll('.item-row').forEach((row, index) => {
|
||||||
|
row.querySelector('td:first-child input').value = index + 1;
|
||||||
|
row.querySelectorAll('input').forEach(input => {
|
||||||
|
if (input.name) {
|
||||||
|
input.name = input.name.replace(/items\[\d+\]/, `items[${index}]`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateRowTotal(row) {
|
||||||
|
const qty = parseFloat(row.querySelector('.qty-input').value) || 0;
|
||||||
|
const price = parseFloat(row.querySelector('.price-input').value) || 0;
|
||||||
|
const total = qty * price;
|
||||||
|
row.querySelector('.total-input').value = total.toFixed(2);
|
||||||
|
updateInvoiceTotal();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateInvoiceTotal() {
|
||||||
|
let total = 0;
|
||||||
|
document.querySelectorAll('.total-input').forEach(input => {
|
||||||
|
total += parseFloat(input.value) || 0;
|
||||||
|
});
|
||||||
|
document.getElementById('invoiceTotal').textContent = total.toFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addItemRow() {
|
||||||
|
if (itemCount >= maxItems) {
|
||||||
|
alert('حداکثر ۹ ردیف میتوانید اضافه کنید.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tbody = document.getElementById('itemsTableBody');
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
row.className = 'item-row';
|
||||||
|
row.innerHTML = `
|
||||||
|
<td class="px-4 py-3"><input type="text" value="${itemCount + 1}" readonly class="w-full rounded border-gray-300 bg-gray-100 text-center text-sm"></td>
|
||||||
|
<td class="px-4 py-3"><input type="text" name="items[${itemCount}][description]" class="w-full rounded border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500 text-sm" placeholder="شرح کالا" required></td>
|
||||||
|
<td class="px-4 py-3"><input type="text" name="items[${itemCount}][hs_code]" class="w-full rounded border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500 text-sm" placeholder="HS Code" required></td>
|
||||||
|
<td class="px-4 py-3"><input type="number" name="items[${itemCount}][quantity]" class="w-full rounded border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500 text-sm qty-input" placeholder="1" min="1" required></td>
|
||||||
|
<td class="px-4 py-3"><input type="number" step="0.01" name="items[${itemCount}][unit_price]" class="w-full rounded border-gray-300 shadow-sm focus:border-orange-500 focus:ring-orange-500 text-sm price-input" placeholder="0.00" min="0" required></td>
|
||||||
|
<td class="px-4 py-3"><input type="number" step="0.01" name="items[${itemCount}][total_usd]" class="w-full rounded border-gray-300 bg-gray-50 text-left text-sm total-input" placeholder="0.00" readonly></td>
|
||||||
|
<td class="px-4 py-3 text-center">
|
||||||
|
<button type="button" onclick="removeItemRow(this)" class="text-red-600 hover:text-red-800 text-sm">حذف</button>
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
tbody.appendChild(row);
|
||||||
|
itemCount++;
|
||||||
|
|
||||||
|
row.querySelector('.qty-input').addEventListener('input', () => calculateRowTotal(row));
|
||||||
|
row.querySelector('.price-input').addEventListener('input', () => calculateRowTotal(row));
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeItemRow(btn) {
|
||||||
|
const row = btn.closest('tr');
|
||||||
|
row.remove();
|
||||||
|
updateRowNumbers();
|
||||||
|
updateInvoiceTotal();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('addItemBtn').addEventListener('click', addItemRow);
|
||||||
|
|
||||||
|
document.querySelectorAll('.qty-input, .price-input').forEach(input => {
|
||||||
|
input.addEventListener('input', () => calculateRowTotal(input.closest('tr')));
|
||||||
|
});
|
||||||
|
|
||||||
|
async function calculatePrice() {
|
||||||
|
const direction = document.getElementById('directionSelect').value;
|
||||||
|
const type = document.getElementById('serviceType').value;
|
||||||
|
const fromCountry = document.getElementById('fromCountry').value;
|
||||||
|
const toCountry = document.getElementById('toCountry').value;
|
||||||
|
const weight = parseFloat(document.getElementById('weight').value) || 0;
|
||||||
|
const volumetricWeight = parseFloat(document.getElementById('volumetricWeight').value) || weight;
|
||||||
|
|
||||||
|
document.getElementById('direction').value = direction;
|
||||||
|
document.getElementById('type').value = type;
|
||||||
|
|
||||||
|
if (!fromCountry || !toCountry || weight <= 0) {
|
||||||
|
document.getElementById('priceResult').classList.add('hidden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (weight > 30) {
|
||||||
|
document.getElementById('spotRateWarning').classList.remove('hidden');
|
||||||
|
document.getElementById('priceResult').classList.add('hidden');
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
document.getElementById('spotRateWarning').classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
const chargeableWeight = Math.max(weight, volumetricWeight);
|
||||||
|
const toCountryIso = document.getElementById('toCountry').selectedOptions[0]?.dataset?.iso || '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/v1/calculate', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
direction: direction === 'export' ? 'Outbound' : 'Inbound',
|
||||||
|
type: type,
|
||||||
|
country_iso: toCountryIso,
|
||||||
|
weight: chargeableWeight,
|
||||||
|
volumetric_weight: chargeableWeight,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
document.getElementById('basePrice').textContent = data.base_price?.toFixed(2) + ' درهم';
|
||||||
|
document.getElementById('totalFee').textContent = Number(data.total_fee).toLocaleString('fa-IR') + ' ریال';
|
||||||
|
document.getElementById('zone').textContent = data.zone;
|
||||||
|
document.getElementById('priceResult').classList.remove('hidden');
|
||||||
|
} else {
|
||||||
|
document.getElementById('priceResult').classList.add('hidden');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Price calculation error:', error);
|
||||||
|
document.getElementById('priceResult').classList.add('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('directionSelect').addEventListener('change', calculatePrice);
|
||||||
|
document.getElementById('serviceType').addEventListener('change', calculatePrice);
|
||||||
|
document.getElementById('fromCountry').addEventListener('change', calculatePrice);
|
||||||
|
document.getElementById('toCountry').addEventListener('change', calculatePrice);
|
||||||
|
document.getElementById('weight').addEventListener('input', calculatePrice);
|
||||||
|
document.getElementById('volumetricWeight').addEventListener('input', calculatePrice);
|
||||||
|
|
||||||
|
document.getElementById('orderForm').addEventListener('submit', function(e) {
|
||||||
|
const itemCount = document.querySelectorAll('.item-row').length;
|
||||||
|
if (itemCount < 1) {
|
||||||
|
e.preventDefault();
|
||||||
|
alert('حداقل یک کالای گمرکی باید وارد شود.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('.total-input').forEach((input, index) => {
|
||||||
|
input.name = `items[${index}][total_usd]`;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
||||||
24
04_Laravel/resources/views/orders/success.blade.php
Normal file
24
04_Laravel/resources/views/orders/success.blade.php
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||||
|
<div class="bg-white shadow rounded-lg p-8 text-center">
|
||||||
|
<div class="mx-auto flex items-center justify-center h-16 w-16 rounded-full bg-green-100 mb-6">
|
||||||
|
<svg class="h-8 w-8 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h2 class="text-2xl font-bold text-gray-900 mb-2">سفارش شما با موفقیت ثبت شد</h2>
|
||||||
|
<p class="text-gray-600 mb-6">کد پیگیری سفارش شما:</p>
|
||||||
|
<div class="bg-gray-50 rounded-lg p-4 mb-6">
|
||||||
|
<span class="text-2xl font-mono font-bold text-orange-600">{{ $shipment->awb_no ?? 'N/A' }}</span>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm text-gray-500 mb-8">کارشناسان ما در اسرع وقت با شما تماس خواهند گرفت.</p>
|
||||||
|
<div class="flex justify-center gap-4">
|
||||||
|
<a href="{{ route('orders.create') }}" class="inline-flex items-center px-4 py-2 bg-orange-600 border border-transparent rounded-md font-semibold text-white hover:bg-orange-700">
|
||||||
|
ثبت سفارش جدید
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Controllers\Api\PricingController;
|
||||||
use App\Http\Controllers\Api\TrackController;
|
use App\Http\Controllers\Api\TrackController;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
@ -8,3 +9,5 @@ use App\Http\Middleware\ApiKeyMiddleware;
|
|||||||
Route::middleware([ApiKeyMiddleware::class])->prefix('v1')->group(function () {
|
Route::middleware([ApiKeyMiddleware::class])->prefix('v1')->group(function () {
|
||||||
Route::get('/track/{awb_no}', [TrackController::class, 'show']);
|
Route::get('/track/{awb_no}', [TrackController::class, 'show']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Route::post('/v1/calculate', [PricingController::class, 'calculate']);
|
||||||
|
|||||||
@ -1,7 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Http\Controllers\OrderController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::get('/', function () {
|
Route::get('/', function () {
|
||||||
return view('welcome');
|
return view('welcome');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Route::get('/order', [OrderController::class, 'create'])->name('orders.create');
|
||||||
|
Route::post('/order', [OrderController::class, 'store'])->name('orders.store');
|
||||||
|
Route::get('/order/success/{shipment}', [OrderController::class, 'success'])->name('orders.success');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user