<?php
namespace Diplix\KMGBundle\Service;
use Diplix\Commons\DataHandlingBundle\Entity\SysLogEntry;
use Diplix\Commons\DataHandlingBundle\Repository\SysLogRepository;
use Diplix\KMGBundle\Controller\Service\Api2Controller;
use Diplix\KMGBundle\Entity\Accounting\Billing;
use Diplix\KMGBundle\Entity\Accounting\CoopMember;
use Diplix\KMGBundle\Entity\Accounting\Job;
use Diplix\KMGBundle\Entity\Address;
use Diplix\KMGBundle\Entity\Availability;
use Diplix\KMGBundle\Entity\Customer;
use Diplix\KMGBundle\Entity\Order;
use Diplix\KMGBundle\Entity\OrderStatus;
use Diplix\KMGBundle\Entity\PaymentType;
use Diplix\KMGBundle\Entity\Role;
use Diplix\KMGBundle\Entity\User;
use Diplix\KMGBundle\Exception\OrderValidationException;
use Diplix\KMGBundle\Helper\ClientConfigProvider;
use Diplix\KMGBundle\PdfGeneration\JobPdf;
use Diplix\KMGBundle\PriceCalculator\AbstractPriceCalculator;
use Diplix\KMGBundle\PriceCalculator\CalculatorService;
use Diplix\KMGBundle\Repository\JobRepository;
use Diplix\KMGBundle\Repository\OrderRepository;
use Diplix\KMGBundle\Service\Accounting\PaymentCalculator;
use Doctrine\ORM\EntityManagerInterface;
use Google\Service\Monitoring\Custom;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Translation\TranslatorInterface;
use GuzzleHttp;
class OrderHandler
{
/** @var EntityManagerInterface */
protected $em;
/** @var Notifier */
protected $notifier;
/** @var OrderRepository */
protected $repo;
protected $USE_TAMI = true;
/** @var TaMiConnector */
protected $tami;
/** @var MailHelper */
protected $mailHelper;
/** @var Security */
protected $security;
/** @var CalculatorService */
protected $calcService;
protected $transactionTag = '';
/** @var PaymentCalculator */
protected $paymentCalculator;
/** @var ClientConfigProvider */
protected $config;
protected $tempDir;
public static $connectionSettings = array(
'connect_timeout' => "5", // abort if no response after X seconds,
'timeout' => "25", // abort if no response after X seconds,
'verify' => false, // we do not care if the cert is valid or not
'http_errors' => true, // throw exception if code != 200
);
public function setTransactionTag($tag)
{
$this->transactionTag = $tag;
$this->notifier->setTransactionTag($tag);
}
public function getStatusObject($id)
{
return $this->em->find(OrderStatus::class,$id);
}
public function __construct(EntityManagerInterface $em,
Notifier $notifier,
TaMiConnector $taMiConnector,
MailHelper $mh,
Security $security,
PaymentCalculator $paymentCalculator,
CalculatorService $calcService,
ClientConfigProvider $configProvider,
$tempDir)
{
$this->notifier = $notifier;
$this->em = $em;
$this->repo = $this->em->getRepository(Order::class);
$this->tami = $taMiConnector;
$this->mailHelper = $mh;
$this->security = $security;
$this->paymentCalculator = $paymentCalculator;
$this->calcService = $calcService;
$this->config = $configProvider;
$this->tempDir = $tempDir;
}
public function enableTami($enableTami)
{
$this->USE_TAMI = $enableTami;
}
protected function updateAvailabilityFromOrder(Order $order)
{
$rep = $this->em->getRepository(Availability::class);
$av = $rep->findOneBy(['createdFromRemoteOrderId'=>$order->getOrderId()]);
if ($order->getAssignedTo()!==null)
{
if ($av===null)
{
$av = new Availability();
//$av->setAvailType(Availability::KMG);
$av->setCreatedFromRemoteOrderId($order->getOrderId());
}
if ($order->getOrderStatus()->getId()===OrderStatus::STATUS_CANCELED)
{
$av->setAvailType(Availability::INFO);
}
else
{
$av->setAvailType(Availability::KMG);
}
$av->setAvailFrom( clone $order->getOrderTime() );
$until = clone($av->getAvailFrom());
$until->add(new \DateInterval('PT1H'));
$av->setAvailUntil( $until );
$av->setMember($order->getAssignedTo());
$av->setExtraInfo(sprintf('#%s: %s',$order->getOrderId(),$order->getOrderStatus()->getName()));
}
else
{
// remove availability for an order without an assigned member
if ($av!==null) $av->setBeDeleted(true);
}
if ($av!==null)
{
$rep->persistFlush($av);
}
}
protected function syncMemberFromOrderToJobIfRequired(Order $order)
{
if ($order->getJob()!==null)
{
// if a job has already been approved - do not change the member anymore
if (!$order->getJob()->isApprovedByAccounting())
{
$order->getJob()->setMember($order->getAssignedTo());
if ($order->getAssignedTo()!==null)
{
$n = $order->getAssignedTo()->getNumber();
$order->getJob()->setRideStyle($order->getAssignedTo()->getDefaultRideStyle());
$order->getJob()->setCarId($n);
$order->getJob()->setDriverId($n);
}
else
{
$order->getJob()->setCarId(0);
$order->getJob()->setDriverId(0);
}
}
else
{
if ($order->getAssignedTo()!==$order->getJob()->getMember())
{
SysLogRepository::logMessage($this->em->getConnection(),SysLogEntry::SYS_INFO,sprintf(
'Warnung: Änderung an Order(%s):assignedTo wurde nicht in Job:member übernommen, da Job:approvedByAccounting=true war',
$order->getOrderId()
));
}
}
}
}
/**
* @param CoopMember $member
* @return GuzzleHttp\Client
* @throws GuzzleHttp\Exception\GuzzleException
* @throws \JsonException
*/
public static function getClientForMemberWhoLoginsAsCustomer(CoopMember $member): GuzzleHttp\Client
{
// check api
$client = new GuzzleHttp\Client(self::$connectionSettings);
$res = $client->request("POST",$member->getXchgTargetUrl(),[
'headers'=> [
"Content-Type"=>"application/json"
],
'body' => json_encode([ // login as customer
"login" => $member->getXchgLogin(),
"password" => $member->getXchgPassword()
], JSON_THROW_ON_ERROR)
]);
$res = json_decode($res->getBody(), true, 512, JSON_THROW_ON_ERROR);
if ($res['success']!==true)
{
throw new \RuntimeException('Ungültige Zugangsdaten für Fremdsystem '.$member->getXchgTargetUrl());
}
if (!isset($res['customer']))
{
throw new \RuntimeException('Account ist nicht als Kunde konfiguriert im Zielsystem');
}
return $client;
}
/**
* @param Customer $customer
* @return GuzzleHttp\Client
* @throws GuzzleHttp\Exception\GuzzleException
* @throws \JsonException
*/
public static function getClientForCustomerWhoLoginsAsMember(Customer $customer): GuzzleHttp\Client
{
// check api
$client = new GuzzleHttp\Client(self::$connectionSettings);
$res = $client->request("POST",$customer->getXchgPlatformUrl(),[
'headers'=> [
"Content-Type"=>"application/json"
],
'body' => json_encode([ // login as customer
"login" => $customer->getXchgLoginAsMember(),
"password" => $customer->getXchgLoginPassword()
], JSON_THROW_ON_ERROR)
]);
$res = json_decode($res->getBody(), true, 512, JSON_THROW_ON_ERROR);
if ($res['success']!==true)
{
throw new \RuntimeException('Ungültige Zugangsdaten für Fremdsystem '.$customer->getXchgPlatformUrl());
}
if (!isset($res['member']))
{
throw new \RuntimeException('Account ist nicht als Mitglied konfiguriert im Zielsystem');
}
return $client;
}
/**
* @param $order int|Order
* @param $member ?int
* @return Order
* @throws \Exception
*/
public function assignMemberToOrder($order, $member): Order
{
/** @var ?CoopMember|?int $member */
if ($member > 0)
{
$member = $this->em->getRepository(CoopMember::class)->findOneBy(['id'=>$member]);
}
else
{
$member = null;
}
$osVermittelt = $this->getStatusObject(OrderStatus::STATUS_VERMITTELT);
$osOffen = $this->getStatusObject(OrderStatus::STATUS_OPEN);
$orderA = $this->repo->findOneBy(['id'=> ($order instanceof Order) ? $order->getId() : $order ]);
$oldMember = $orderA->getAssignedTo();
$this->em->getConnection()->setAutoCommit(false);
$this->em->transactional(function($em) use($order,$osVermittelt,$osOffen,$member) {
$order = $this->repo->findOneBy(['id'=> ($order instanceof Order) ? $order->getId() : $order ]);
if ($order->getOrderStatus()->getId()>3)
{
throw new \Exception('Fahrt bereits storniert oder im falschen Status. Änderung nicht mehr möglich.');
}
if ($order->getOrderStatus()->getId()==3) //3 = erledigt
{
if ($order->getOrderTime() < new \DateTime('-2 day'))
{
throw new \Exception('Fahrt bereits erledigt und mehr als 48h alt. Änderung nicht mehr möglich.');
}
}
if (($order->isAssignmentConfirmed())&&($member!==null))
{
throw new \Exception('Fahrtannahme wurde bereits bestätigt vom Mitglied. Bitte zunächst die Zuordnung entfernen sofern ein Wechsel gewünscht ist.');
}
if ( ($order->getAssignedTo()!==null)&&($member!==null) )
{
throw new \Exception("Fahrt ist bereits einem Mitglied zugeordnet. Bitte zunächst die Zuordnung entfernen.");
}
$order->setAssignmentStatus(Order::AS_NONE);
if ($member instanceof CoopMember)
{
if ($member->getXchgTargetUrl()!=="")
{
$this->sendToPlatform($order,$member);
}
$order->setOrderStatus( $osVermittelt );
$order->setAssignedTo($member);
$order->setAssignmentConfirmed(false);
}
else
{
if (($order->getAssignedTo()!==null) && ($order->getAssignedTo()->getXchgTargetUrl()!==""))
{
$this->cancelInPlatform($order);
$order->setXchgTo(null);
if ($order->getXchgStatus() !== Order::XCHG_RECEIVED_FROM_OTHER) // falls Fahrt von Fremdsystem darf der Status nicht geändert werden
{
$order->setXchgStatus(Order::XCHG_NONE);
$order->setXchgOrderId('');
}
$order->setXchgConfirmed(false);
}
$order->setOrderStatus($osOffen);
$order->setAssignedTo(null);
$order->setAssignmentConfirmed(false);
}
$this->syncMemberFromOrderToJobIfRequired($order);
});
$this->em->getConnection()->setAutoCommit(true);
// refresh order
$order = $this->repo->findOneBy(['id'=> ($order instanceof Order) ? $order->getId() : $order ]);
// update avail
$this->updateAvailabilityFromOrder($order);
// notify
$this->notifier->triggerOrderUpdateForMember($order,Notifier::M_CONFIRMATION_REQUIRED);
if ($oldMember!==null)
{
$this->notifier->notifyMemberAboutOrderRemoval($oldMember,$order);
}
return $order;
}
/**
* @return PaymentType
* @throws \Exception
*/
protected function getDefaultPaymentType(User $user, Customer $customer = null)
{
if ($customer === null) $customer = $user->getCustomer();
$pt = $customer->getDefaultPaymentType();
if ($pt === null)
{
$pt = $customer->getPaymentTypes()->get(0);
if ($pt === null) throw new \Exception("No paymentType set");
}
return $pt;
}
/**
* @param User $u
* @param Customer|null $c
* @param int $orderStatus
* @param PaymentType|int|null $paymentTypeOrId
* @param bool $autoFillOrderer
* @return Order
* @throws \Doctrine\ORM\ORMException
*/
public function getNewOrder(User $u, Customer $c = null, $orderStatus = OrderStatus::STATUS_DRAFT, $paymentTypeOrId = null, $autoFillOrderer=true)
{
$row = new Order();
// owner
$row->setBeOwner($u);
$row->setCustomer($c ?? $row->getBeOwner()->getCustomer());
// initial status
$row->setOrderStatus( $this->getStatusObject($orderStatus));
$row->setPersonCount(1);
// start with 2 empty addresses addresses
$row->addAddress(new Address($row->getCustomer(),0)); // empty start
$row->addAddress(new Address($row->getCustomer(),1)); // empty destination
// orderer details
if ($autoFillOrderer) // do not rely on the users preference in user->autofillOrdererDetails at this point
{
$row->setOrdererForename( $u->getFirstName() );
$row->setOrdererName($u->getLastName());
$row->setOrdererMail($u->getEmail());
$row->setOrdererPhone($u->getPhone());
}
// payment type
if ($paymentTypeOrId!==null)
{
if ($paymentTypeOrId instanceof PaymentType)
$row->setPaymentType( $paymentTypeOrId );
else
$row->setPaymentType( $this->em->getReference(PaymentType::class,$paymentTypeOrId) );
}
else
{
$row->setPaymentType($this->getDefaultPaymentType($u,$c));
}
// default price list
$row->setPriceList( $u->getCustomer()->getDefaultPriceList() );
// default to next day as order time
$offset = $this->config->getOrderTimeOffsetOnNewOrder();
if ($offset!==null)
{
$dt = new \DateTime("now");
if ($offset > 0)
{
$dt->add(new \DateInterval(sprintf("P%dD",$offset)));
}
$row->setOrderTime($dt);
}
// PKW is default car type
$row->setCarType(Order::CARTYPE_PKW);
return $row;
}
protected function processChildOrder(Order $row)
{
if ($row->getReferencedParentOrder()!==null)
{
// the order is a child itsself.
return;
}
// actually this can only happen for BLUM orders
if (in_array($row->getDirection(), [Order::DIRECTION_TWOWAY,Order::DIRECTION_TWOWAY_REVERSE]))
{
$childRow = Order::createOrUpdateChildOrder($row, $row->getChildOrder());
if (is_null($childRow->getOrderStatus()))
{
$childRow->setOrderStatus($this->em->find(OrderStatus::class, OrderStatus::STATUS_DRAFT));
}
$this->repo->persistFlush($childRow);
}
else
{
$child = $row->getChildOrder();
if ($child !== null) {
if (($this->USE_TAMI) && ($child->getRemoteStatus() !== Order::REMOTE_PENDING))
{
if (!$this->tami->cancelOrder($child))
{
SysLogRepository::logError($this->em->getConnection(),"Fehler beim Stornieren des Unterauftrags in TAMI !",$child);
}
}
$child->setReferencedParentOrder(null);
$child->setBeDeleted(true);
$this->repo->flush($child);
}
}
}
// store order (do not initiate)
public function storeOrUpdate(Order $order)
{
/*
if ($this->em->contains($order))
{
throw new \LogicException('storeNewOrder(): Cannot be used with an existing order !');
}
*/
/*
if ($order->getOrderStatus()->getId()!==OrderStatus::STATUS_DRAFT)
{
throw new \LogicException('storeNewOrder(): order is not a draft !');
}
*/
// ensure that all Addresses match our customer and user
/** @var Address $a */
foreach ($order->getAddressList() as $a) {
$a->setCustomer($order->getCustomer());
$a->setBeOwner($order->getBeOwner());
$a->setOwningOrder($order);
}
// ensure that the pricelist is set and let it process the order
if ($order->getPriceList()===null)
{
throw new OrderValidationException("Preisliste nicht gesetzt.");
}
$pti = $order->getPaymentType()->getId();
$pta = array_map(function(PaymentType $paymentType) { return $paymentType->getId(); }, $order->getPriceList()->getPaymentTypes()->toArray());
if (!in_array($pti,$pta))
{
$ptaNames = array_map(function(PaymentType $paymentType) { return $paymentType->getName(); }, $order->getPriceList()->getPaymentTypes()->toArray());
throw new OrderValidationException(sprintf("Die Preisliste ist mit der gewählten Zahlart (%s) leider nicht nutzbar. Möglich: %s",$order->getPaymentType()->getName(),implode(",",$ptaNames)));
}
$orderCreated = $order->getBeCreated() ?? new \DateTime();
if ( ($order->getPriceList()->getValidUntil()!==null) && ($orderCreated->getTimestamp() > $order->getPriceList()->getValidUntil()->getTimestamp()) )
{
throw new OrderValidationException(sprintf('Preiseliste ist nicht mehr gültig für den Auftrag erzeugt am %s.',$orderCreated->format('d.m.Y')));
}
if ( ($order->getPriceList()->getValidSince()!==null) && ($orderCreated->getTimestamp() < $order->getPriceList()->getValidSince()->getTimestamp()) )
{
throw new OrderValidationException(sprintf('Preiseliste ist noch nicht gültig für einen Auftrag erzeugt am %s.',$orderCreated->format('d.m.Y')));
}
$plc = AbstractPriceCalculator::getCalculator($order->getPriceList());
$plc->postProcessOrder($order);
if ($order->getLastEstimatedDistance() === 0)
{
// if the lastEstimatedDistance field is empty
// (e.g. because the order came via api or with a PL which does not require a distance)
// we try to get it here
$flatWaypoints = [ $order->getPriceList()->getHomeAddress() ]; // start/endpoint
$addresses = [];
foreach ($order->getAddressList() as $a) {
$flatWaypoints[] = CalculatorService::addressToFlatString(json_decode(json_encode($a),true));
$addresses[] = json_decode(json_encode($a),true);
}
try {
$distances = $this->calcService->getDistance($flatWaypoints);
$order->setLastEstimatedDistance( $this->calcService->getDistanceSumInKm($distances) );
if ($order->getLastEstimatedPrice() == 0)
{
$pp = $this->calcService->estimatePrice(
$order->getPriceList(),
$addresses,
[],
$order->getLastEstimatedDistance(),
$order->getCustomer()
);
$order->setLastEstimatedPrice($pp);
}
}
catch (\Throwable $ex)
{
// do not crash on api error (e.g. OVER_QUERY_LIMIT)
$order->setInternalComment($order->getInternalComment()."\n".$ex->getMessage());
}
}
$this->repo->persistFlush($order);
// deleted addresses are still in our entity
$order->removeDeletedAddressesFromList();
// todo:blum child order handling -> CHECK FUNCTIONALITY
$this->processChildOrder($order);
}
public static function transactionalWithTableLock(EntityManagerInterface $em, $lockTableForWrite, callable $callback)
{
try {
// LOCK TABLES is not transaction-safe and implicitly commits any active transaction before attempting to lock the tables.
// START TRANSACTION releases existing locks
// therefore we neeed to disable autocommit instead of explicitly starting a transaction
// ROLLBACK doesn’t release table locks.
$em->getConnection()->setAutoCommit(false);
$em->getConnection()->exec('LOCK TABLES '.$lockTableForWrite.' WRITE;');
$callback($em);
$em->flush();
$em->getConnection()->commit();
$em->getConnection()->exec('UNLOCK TABLES;'); // UNLOCK TABLES implicitly commits any active transaction, but only if LOCK TABLES have been used to acquire table locks
}
catch (\Throwable $e) {
$em->close();
$em->getConnection()->rollBack();
$em->getConnection()->exec('UNLOCK TABLES;');
throw $e;
}
$em->getConnection()->setAutoCommit(true);
}
// Bestellung auslösen / falls bereits ausgelöst entsprechend aktualisieren
public function initiateOrder(Order $order, $suppressMail = false)
{
$newOrderCreated = false;
if ($order->getOrderStatus()->getId()===OrderStatus::STATUS_DRAFT)
{
if ($order->getOrderId()!=='')
{
throw new OrderValidationException('initiateOrder(): order already has an order id !');
}
$open = $this->em->find(OrderStatus::class,OrderStatus::STATUS_OPEN);
self::transactionalWithTableLock($this->em, "orders", function(EntityManagerInterface $em) use($order,$open)
{
$newId = $this->repo->getNewOrderId( $order->getOrderTime() );
$order->setOrderId($newId);
$order->setOrderStatus($open);
$order->setOrderInitiatedOn(new \DateTime());
});
// $this->em->transactional(function(EntityManagerInterface $em) use($order,$open) {
// $em->getConnection()->exec('LOCK TABLES orders WRITE;');
// $newId = $this->repo->getNewOrderId( $order->getOrderTime() );
// $order->setOrderId($newId);
// $order->setOrderStatus($open);
// $order->setOrderInitiatedOn(new \DateTime());
// $em->getConnection()->exec('UNLOCK TABLES;');
// });
$newOrderCreated = true;
}
else
{
if ($order->getOrderId()==='')
{
throw new OrderValidationException('initiateOrder(): order has no order id !');
}
}
// update existing tami
if ( ($this->USE_TAMI) /*|| ($order->getRemoteStatus() !== Order::REMOTE_PENDING)*/ )
{
if (!$this->tami->submitOrder($order))
{
SysLogRepository::logError($this->em->getConnection(),"Übertragung nach TaMi fehlgeschlagen: ".$order->getRemoteResult(),$order);
}
}
// create job
if ($order->getJob()===null)
{
$order->setJob( Job::createForOrder($order ) );
if ($this->config->isJobAlwaysRecalculateCustomerPrice())
{
$order->getJob()->setRecalculateCustomerPrice(true);
}
}
else
{
$order->getJob()->updateFromOrder($order);
}
$this->em->flush(); // update order
// email confirmation
if (!$suppressMail)
{
try
{
$this->mailHelper->OrderConfirmationMail($order);
}
catch (\Throwable $ex)
{
SysLogRepository::logError($this->em->getConnection(),"Mailzustellung bei Bestellung fehlgeschlagen: ".$ex->getMessage(),$order,0,'Mail');
}
}
// emit notification
$this->notifier->notifyAboutOrder($order, $newOrderCreated);
$this->notifier->notifyTelegramAboutOrder($order,$newOrderCreated);
// update availability
$this->updateAvailabilityFromOrder($order);
// todo: child order -> move from controller to here if possible
// problem: when not doing in controller, no redirect to actual order occurs, "/new" order is created everytime on a
// subsequent error
// if ($order->getChildOrder()!==null)
// {
// $this->initiateOrder($order->getChildOrder());
// }
}
public function isLagTooLow(\DateTime $dt)
{
return false; // TODO: re-enable temporarily removed check
if ($this->security->isGranted(Role::NO_TIME_RESTRICTION))
{
return false;
}
$earliest = new \DateTime();
$earliest->add(new \DateInterval(sprintf("PT%dH", Order::TIME_LAG_HOURS)));
return ($dt < $earliest);
}
public function ensureThatOrderCanBeCancelled(Order $row): void
{
if (null !== $row->getReferencedParentOrder())
{
throw new OrderValidationException("Diese Fahrt wurde automatisch angelegt und kann nicht gelöscht/storniert werden. Bitte bearbeiten Sie den Hauptauftrag !");
}
if ($this->IsLagTooLow($row->getOrderTime()))
{
throw new OrderValidationException('order.edit-not-possible-lag');
}
if (
(!$this->security->isGranted(Role::EDIT_CLOSED_ORDERS))
&&
(!in_array($row->getOrderStatus()->getId(), [OrderStatus::STATUS_OPEN, OrderStatus::STATUS_DRAFT, OrderStatus::STATUS_VERMITTELT], true))
)
{
throw new OrderValidationException('Fahrt kann nicht mehr storniert werden.');
}
if ($row->isAssignmentConfirmed() && ($row->getAssignedTo()!==null))
{
throw new OrderValidationException('Fahrt ist bereits zugeordnet. Bitte rufen Sie die Disposition an um diesen Auftrag zu stornieren');
}
if ($row->getChildOrder()!==null)
{
if ($this->IsLagTooLow( $row->getChildOrder()->getOrderTime() ))
{
throw new OrderValidationException('Unterauftrag: order.edit-not-possible-lag');
}
if ($row->getChildOrder()->isAssignmentConfirmed() && ($row->getChildOrder()->getAssignedTo()!==null))
{
throw new OrderValidationException('Unterauftrag ist bereits zugeordnet. Bitte rufen Sie die Disposition an um diesen Auftrag zu stornieren');
}
}
}
public function cancelOrder(Order $row)
{
$this->ensureThatOrderCanBeCancelled($row);
$storniert = $this->em->find(OrderStatus::class,OrderStatus::STATUS_CANCELED);
$this->em->getConnection()->setAutoCommit(false);
$this->em->transactional(function(EntityManagerInterface $em) use($row,$storniert) {
$em->getConnection()->exec('LOCK TABLES orders WRITE;');
$row->setOrderStatus($storniert);
if ($row->getChildOrder()!==null)
{
$row->getChildOrder()->setOrderStatus($storniert);
}
$em->getConnection()->exec('UNLOCK TABLES;');
});
$this->em->getConnection()->setAutoCommit(true);
// update existing tami
if ( ($this->USE_TAMI) && ($row->getRemoteStatus() !== Order::REMOTE_PENDING))
{
if (!$this->tami->cancelOrder($row))
{
SysLogRepository::logError($this->em->getConnection(),"Fehler beim Stornieren des Auftrags in TAMI !",$row);
}
if ( ($row->getChildOrder()!==null) && ($this->USE_TAMI) && ($row->getChildOrder()->getRemoteStatus() !== Order::REMOTE_PENDING) )
{
if (!$this->tami->cancelOrder($row))
{
SysLogRepository::logError($this->em->getConnection(),"Fehler beim Stornieren des Unterauftrags in TAMI !",$row->getChildOrder());
}
}
}
// update job
if ($row->getJob()!==null)
{
$jr = $this->em->getRepository(Job::class);
$jr->discardJobs([$row->getJob()->getId()],true);
}
// update avail
$this->updateAvailabilityFromOrder($row);
// notify
$this->notifier->notifyAboutOrder($row,false);
$this->notifier->notifyTelegramAboutOrder($row,false);
// update remote status
if ($row->getJob()!==null)
{
$member = $row->getJob()->getMember();
if ($member instanceof CoopMember) {
if ($member->getXchgTargetUrl() !== "") {
$this->cancelInPlatform($row);
}
}
}
}
public function triggerJobCalculation(Job $row)
{
/** @var JobRepository $repo */
$repo = $this->em->getRepository(Job::class);
$this->paymentCalculator->calculateTotals($row, $row->isBilled(Billing::TYPE_CUSTOMER),$row->isBilled(Billing::TYPE_MEMBER));
$msg = [];
JobRepository::detectReadyForBilling($row, $msg);
return $msg;
}
public function getJobPdf(Order $row, User $requestor, $source='',$enforceMember=false)
{
if ($enforceMember)
{
if ($requestor->getMember()===null) throw new \Exception('Ihrem Nutzer ist kein Mitglied zugeordnet');
if ( ($row->getAssignedTo()=== null) ||($row->getAssignedTo()->getId() !== $requestor->getMember()->getId())) throw new \Exception('Fahrt ist Ihnen nicht zugeordnet');
if (!$row->isAssignmentConfirmed()) throw new \Exception('Bitte bestätigen Sie die Fahrtannahme');
}
$fn = $row->getOrderId(). '-'. $row->getId(). '-'. date('Y-m-d_H_i_s').'.pdf';
$pdfFile = $this->tempDir. $fn ;
$pdf = new JobPdf($this->config);
$pdf->render($row);
$pdf->Output($pdfFile);
SysLogRepository::logMessage($this->em->getConnection(),SysLogEntry::SYS_INFO,'Job-PDF downloaded ('.$source.')',[
'orderId'=>$row->getOrderId(),
'memberId'=>$requestor->getMember()!== null ? $requestor->getMember()->getId() : null,
'source'=>$source
],$requestor->getId(),$row->getId());
if ($row->getJobPdfRequestedOn()===null)
{
// prevent a flush, only modify the single field
$this->em->getConnection()->prepare('UPDATE orders SET job_pdf_requested_on = :req WHERE id = :id AND job_pdf_requested_on IS NULL')->execute([
'req' => (new \DateTime())->format('Y-m-d H:i:s'),
'id' => $row->getId(),
]);
}
$this->notifier->notifyAboutOrder($row,false);
return $pdfFile;
}
public function sendToPlatform(?Order $row, ?CoopMember $member)
{
$repo = $this->em->getRepository(Order::class);
if ($row === null)
{
throw new \RuntimeException("Unbekannte Bestellung");
}
if ($member === null)
{
throw new \RuntimeException("Unbekanntes Mitglied");
}
if ($row->getOrderStatus()->getId() !== OrderStatus::STATUS_OPEN)
{
throw new \RuntimeException('Bestellung ist nicht im Status Offen.');
}
if ($row->getXchgStatus() !== Order::XCHG_NONE)
{
throw new \RuntimeException('Bestellung ist bereits Transferquelle oder -Ziel. Weitervermittlung nicht möglich');
}
if ($row->getAssignedTo()!==null)
{
throw new \RuntimeException('Fahrt ist aktuell einem Mitglied zugeordnet. Bitte zuerst die Zuordnung entfernen.');
}
$client = OrderHandler::getClientForMemberWhoLoginsAsCustomer($member);
// PLACE ORDER
$item = $repo->getSingleAsArray($row->getId());
Api2Controller::prepare4json($item,false);
unset($item['id']);
unset($item['paymentType']);
$dt = new \DateTime($item['orderTime']);
$item['orderTime_date'] = $dt->format('Y-m-d');
$item['orderTime_time'] = $dt->format('H:i');
$data = [
"login" => $member->getXchgLogin(),
"password" => $member->getXchgPassword(),
"commit"=>true,
"xchg"=>true,
"original_order_id" => $item['orderId']
];
$data = array_merge($data,$item);
$res = $client->request("POST",rtrim($member->getXchgTargetUrl(),'/')."/orders/place",[
'headers'=> [
"Content-Type"=>"application/json"
],
'body' => json_encode($data, JSON_THROW_ON_ERROR)
]);
$res = json_decode($res->getBody(), true, 512, JSON_THROW_ON_ERROR);
if ($res['success']!==true)
{
throw new \RuntimeException('Fehler bei der Übertragung.'.var_export($res,true));
}
$row->setXchgTo($member);
$row->setXchgStatus(Order::XCHG_SENT_TO_OTHER);
$row->setXchgOrderId($res['data']);
$repo->flush($row);
}
public function acceptOrDeclineToPlatformAndSaveOrderState(Order $row, $action)
{
if ($row->getXchgStatus() !== Order::XCHG_RECEIVED_FROM_OTHER)
{
throw new \RuntimeException('Fahrt nicht aus Fremdsystem');
}
if ($row->isXchgConfirmed())
{
throw new \RuntimeException('Fahrt ist bereits bestätigt.');
}
if (!in_array($action,['accept','decline']))
{
throw new \RuntimeException('Unbekannte Aktion');
}
if ($action==="decline")
{
$canCancel = true;
$canMsg = '';
try {
$this->ensureThatOrderCanBeCancelled($row);
}
catch (\Throwable $ex)
{
$canCancel = false;
$canMsg = $ex->getMessage();
}
if (!$canCancel)
{
throw new \RuntimeException('Stornierung der Fahrt nicht möglich. Daher Ablehnung nicht möglich. (Meldung:'.$canMsg.')');
}
}
$client = self::getClientForCustomerWhoLoginsAsMember($row->getCustomer());
$res = $client->request("POST",rtrim($row->getCustomer()->getXchgPlatformUrl(),'/')."/respond-assignment",[
'headers'=> [
"Content-Type"=>"application/json"
],
'body' => json_encode(
[
"login" => $row->getCustomer()->getXchgLoginAsMember(),
"password" => $row->getCustomer()->getXchgLoginPassword(),
"action"=> $action,
"commit"=>true,
"orderId"=> $row->getXchgOrderId(),
"xchg"=>true
]
, JSON_THROW_ON_ERROR)
]);
$res = json_decode($res->getBody(), true, 512, JSON_THROW_ON_ERROR);
if ($res['success']!==true)
{
throw new \RuntimeException('Fehler bei der Übertragung.'.var_export($res,true));
}
if ($action==="accept")
{
$row->setXchgConfirmed(true);
$this->em->flush();
}
else
if ($action ==="decline")
{
$this->cancelOrder($row);
}
}
protected function cancelInPlatform(Order $row)
{
$client = OrderHandler::getClientForMemberWhoLoginsAsCustomer($row->getAssignedTo());
$res = $client->request("POST",rtrim($row->getAssignedTo()->getXchgTargetUrl(),'/')."/orders/cancel",[
'headers'=> [
"Content-Type"=>"application/json"
],
'body' => json_encode(
[
"login" => $row->getAssignedTo()->getXchgLogin(),
"password" => $row->getAssignedTo()->getXchgPassword(),
"orderId"=> $row->getXchgOrderId(),
"commit"=>true,
"xchg"=>true
]
, JSON_THROW_ON_ERROR)
]);
$res = json_decode($res->getBody(), true, 512, JSON_THROW_ON_ERROR);
if ($res['success']!==true)
{
throw new \RuntimeException('Fehler bei der Übertragung.'.var_export($res,true));
}
}
}