<?php
namespace App\Http\Controllers\Api\TransporteBlanco\V1;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Delivery\PedidosYaController;
use App\Http\Requests\Api\TransporteBlanco\PricingRequest;
use App\Models\Address;
use App\Models\Product;
use App\Models\ShippingCost;
use App\Models\TransporteBlancoPueblo;
use App\Models\TransporteBlancoZona;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Support\Facades\Log;
class PricingController extends Controller {
private const AVAILABLE_AREAS = [
'Santo Domingo',
'Santo Domingo Este',
'Santo Domingo Norte',
'Santo Domingo Oeste',
'Distrito Nacional',
'Pedro Brand',
'Los Alcarrizos',
'San Antonio de Guerra',
'Boca Chica'
];
private const MAX_VOLUME = (19 * 2.54) * (12 * 2.54) * (12 * 2.54);
private const TB_ADDRESS_SD_RD = [
'address' => 'Av. Los Beibolistas, Esq. Jafres',
'city' => 'Santo Domingo',
'state' => 'Santo Domingo',
'latitude' => 18.48402381193991,
'longitude' => -69.982946712279,
'phone' => '+8093791670',
];
private static ShippingCost $shippingCost;
public function pricing(PricingRequest $request) {
$response = [];
$customerId = $request->customerId;
$product = Product::find($request->productId);
$quantity = $request->quantity;
$response['estimated_pickup_time'] = self::estimatePickupTime();
$town = strtoupper($request->city);
$buyerAddress = self::getBuyerAddress($customerId);
$addressForPedidosYa = Address::make(self::TB_ADDRESS_SD_RD);
$body = self::buildBody($request, $product, $quantity, $addressForPedidosYa);
$pueblo = TransporteBlancoPueblo::where('nombre', $town)
->orWhere(function ($query) use ($request) {
$query->where('latitud', $request->latitude)
->where('longitud', $request->longitude);
})->first();
if (self::isLocalDelivery($buyerAddress)) {
return response()->json(self::createLocalDelivery($response, $request, $body, $product));
}
if (!$pueblo) {
$ciudad = TransporteBlancoZona::where('nombre', 'like', '%' . $request->state . '%')->first();
Log::alert($request->state);
Log::alert($ciudad);
if ($ciudad) {
\Log::alert('ciudad');
$response = self::buildResponse(
$response,
$ciudad->nombre,
self::calcCosts($product, $customerId, $quantity, $ciudad, $body, false),
self::estimateDropOffTime($response['estimated_pickup_time'], false)
);
}
\Log::alert('nada');
} else {
\Log::alert('pueblo');
$response = self::buildResponse(
$response,
$pueblo->nombre,
self::calcCosts($product, $customerId, $quantity, $pueblo, $body),
self::estimateDropOffTime($response['estimated_pickup_time'], true, $pueblo)
);
}
return response()->json($response);
}
private static function estimatePickupTime() {
$date = Carbon::today()->timezone('America/Santo_Domingo')->addDay();
while ($date->isWeekend()) {
$date->addDay();
}
return $date->format('Y-m-d H:i:s');
}
private static function getBuyerAddress($customerID): Address {
return User::find($customerID)->addresses()->where('set_default', 1)->first();
}
private static function buildBody($request, $product, $quantity, $addressForPedidosYa) {
return [
'customerId' => $request->customerId,
'product' => $product,
'quantity' => $quantity,
'totalWeight' => self::calcTotalWeight($product, $quantity),
'totalVolume' => self::extractVolume($product),
'sellerAddress' => self::getSellerAddress($product),
'buyerAddress' => $addressForPedidosYa,
'availableAreas' => self::AVAILABLE_AREAS,
];
}
private static function calcTotalWeight(Product $product, $quantity): float {
return $product->weight * $quantity;
}
private static function extractVolume(Product $product): float {
$choise_options = json_decode($product->choice_options);
$envelope = trim(str_replace('SOBRE', '', $choise_options[0]->values[0]));
return match ($envelope) {
'A' => self::calcVolume(4.7, 8.4, 2.4),
'B' => self::calcVolume(5.9, 8.4, 2.4),
'C' => self::calcVolume(7, 10.4, 2.4),
'D' => self::calcVolume(8.6, 10.4, 2.4),
'E' => self::calcVolume(9, 13.3, 2.4),
'F' => self::calcVolume(9.4, 13.1, 2.4),
'G' => self::calcVolume(10.6, 8.4, 14.1),
'H' => self::calcVolume(11.8, 17.5, 2.4),
'I' => self::calcVolume(13.7, 18.5, 2.4),
'J' => self::calcVolume(7, 6.4, 2.4),
default => 0.00,
};
}
private static function calcVolume(int|float $length, int|float $width, int|float $height): float {
$length_to_cm = $length * 2.54;
$width_to_cm = $width * 2.54;
$height_to_cm = $height * 2.54;
return $length_to_cm * $width_to_cm * $height_to_cm;
}
private static function getSellerAddress(Product $product): Address|bool {
return $product->user->addresses()->where('set_default', 1)->first();
}
private static function isLocalDelivery($buyerAddress): bool {
return in_array($buyerAddress->city, self::AVAILABLE_AREAS);
}
private static function createLocalDelivery($response, $request, $body, $product) {
$cart = $product->carts()->where('user_id', $request->customerId)->first();
if (!$cart) {
throw new HttpResponseException(response()->json(['message' => 'NOT_PRODUCT_IN_CART'], 404));
}
if (PedidosYaController::checkAvailability($body)) {
$body['isTransferred'] = true;
$pedidosYa = PedidosYaController::make($body);
$price = 240.00 + $pedidosYa['delivery']['starter_price'];
self::createShippingCost($pedidosYa, $cart, $body, $price);
} else {
$body['isTransferred'] = false;
$price = 240.00 + 350.00;
self::createShippingCost(null, $cart, $body, $price);
}
return self::buildResponse(
$response,
$request->city,
self::calcCosts($product, $request->customerId, $request->quantity, $request->city, $body, false),
self::estimateDropOffTime($response['estimated_pickup_time'], false)
);
}
private static function createShippingCost($pedidosYa, $cart, $body, $price = 0.00) {
$price = self::calcExtraPrice($cart->product->weight, self::extractVolume($cart->product), $price);
if (($body['product']->unit_price * $body['quantity']) > 2000.00) {
$secureDifference = ($body['product']->unit_price * $body['quantity']) - 2000.00;
}
$shippingCost = ShippingCost::make([
'starter_price' => $price,
'ending_price' => $price + ($price * 0.25),
'pedidosya_secure_difference' => $secureDifference ?? 0.00,
'cart_id' => $cart->id,
'shipping_company' => 'Transporte Blanco'
]);
if ($body['isTransferred']) {
$isSaved = $cart->shippingCosts()
->where('shipping_company', 'TRANSPORTE BLANCO')
->exists();
if ($isSaved) {
$oldShippingCost = $cart->shippingCosts()
->where('shipping_company', 'TRANSPORTE BLANCO')
->where('require_transfer', true)
->first();
if ($oldShippingCost->expiration_date < Carbon::now('America/Santo_Domingo')) {
$response = PedidosYaController::make($body);
$shippingCost->update([
'shipping_id' => $response['delivery']['estimateId'],
'delivery_offer_id' => $response['delivery']['deliveryOfferId'],
'pedidosya_secure_difference' => $secureDifference ?? 0.00,
'expiration_date' => $response['delivery']['confirmationTimeLimit'],
'require_transfer' => $body['isTransferred']
]);
}
$shippingCost = $oldShippingCost;
} else {
$shippingCost->fill([
'shipping_id' => $pedidosYa['delivery']['estimateId'],
'delivery_offer_id' => $pedidosYa['delivery']['deliveryOfferId'],
'pedidosya_secure_difference' => $secureDifference ?? 0.00,
'expiration_date' => $pedidosYa['delivery']['confirmationTimeLimit'],
'require_transfer' => $body['isTransferred']
]);
$shippingCost->save();
}
} else {
if ($cart->shippingCosts()->where('shipping_company', 'TRANSPORTE BLANCO')->exists()) {
$cart->shippingCosts()
->where('shipping_company', 'TRANSPORTE BLANCO')
->first()
->delete();
}
$shippingCost->save();
}
self::$shippingCost = $shippingCost;
}
private static function calcExtraPrice($totalWeight, $totalVolume, $initialPrice) {
$price = $initialPrice;
if ($totalWeight > 40.00 && $totalWeight < 150.00) {
$totalWeightExceeded = $totalWeight - 40.00;
$extraWeightPrice = $totalWeightExceeded * 10.00;
$price += $extraWeightPrice;
}
if ($totalVolume > self::MAX_VOLUME) {
$actualVolumeOnPie = $totalVolume / 30.48;
$maxVolumeOnPie = self::MAX_VOLUME / 30.48;
$volumeExceeded = $actualVolumeOnPie - $maxVolumeOnPie;
$extraVolumePrice = $volumeExceeded * 30.00;
$price += $extraVolumePrice;
}
return $price;
}
private static function buildResponse($response, $to, $price, $dropoff_time) {
$pickup_time = $response['estimated_pickup_time'];
unset($response['estimated_pickup_time']);
return [
'to' => $to,
'starter_price' => $price,
'ending_price' => (int)number_format($price + ($price * 0.25), 2),
'estimated_pickup_time' => $pickup_time,
'estimated_dropoff_time' => $dropoff_time,
'shippingCostId' => self::$shippingCost->id
];
}
private static function calcCosts(Product $product, $customerID, $quantity, $to, $body, $isTown = true) {
// Addresses
$buyerAddress = self::getBuyerAddress($customerID);
// Weight on Pounds
$totalWeight = self::calcTotalWeight($product, $quantity);
$totalVolume = self::extractVolume($product) * $quantity;
$cart = $product->carts()->where('user_id', $customerID)->first();
if (!$cart) {
throw new HttpResponseException(response()->json(['message' => 'NOT_PRODUCT_IN_CART'], 404));
}
$isLocal = self::isLocalDelivery($buyerAddress);
if ($isTown) {
if (PedidosYaController::checkAvailability($body)) {
$body['isTransferred'] = true;
$pedidosYa = PedidosYaController::make($body);
$price = 240 + $pedidosYa['delivery']['starter_price'];
self::createShippingCost($pedidosYa, $cart, $body, $price);
} else {
$isLocal ? $price = 240.00 + 350.00 : $price = $to->zona->categoria->precio;
}
} else {
if (PedidosYaController::checkAvailability($body)) {
$pedidosYa = PedidosYaController::make($body);
$price = 240 + $pedidosYa['delivery']['starter_price'];
} else {
$isLocal ? $price = 240.00 + 350.00 : $price = $to->categoria->precio;
}
}
return self::calcExtraPrice($totalWeight, $totalVolume, $price);
}
private static function estimateDropOffTime($pickupTime, $isTown = true, $town = null, $isLocal = false) {
// Tomar la fecha estimada de entrega a la paquetera
$date = Carbon::createFromDate($pickupTime)->timezone('America/Santo_Domingo');
// Si es una ciudad, calcular el tiempo de entrega a fecha actual + 5 días files
// Si es un pueblo, calcular el tiempo de entrega al proximal día de disponibilidad
if ($isLocal) $isTown = true;
if ($isTown) {
$isLocal ?
$available_days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'] :
$available_days = array_map('ucfirst', json_decode($town->dias_de_entrega));
// Si el día de recogida es un día de entrega, agrega un día
if (in_array($date->englishDayOfWeek, $available_days)) {
$date->addDay();
}
// Calcula el próximo día de entrega
while (!in_array($date->englishDayOfWeek, $available_days)) {
$date->addDay();
}
} else {
$date->addRealDays(5);
while ($date->isWeekend()) {
$date->addDay();
}
}
return $date->format('Y-m-d H:i:s');
}
}
|