/** * Creates transactions from clockings. * * Plugin event "transaction.add" data: * - "user": { ... } // The user object * - "clockings": [ ... ] * - "booking_types": { identifier: {...}, ... } * - "bookings": {} // Receives the bookings to be created * - "transactions": {} // Receives the transactions to be created * * @param array $clockingIds * @param bool $commit Optional. If FALSE, only data about the transaction * will be returned without saving them to the database. That data can * be used to manually create a transaction. * Default is TRUE. * @return array An array with IDs of clocking that have been booked. * @throws Exception */ public function do_add(array $clockingIds, $commit = true) { if (empty($clockingIds)) { throw new Exception('No clockings specified.'); } $usedClockingIds = array(); // Return value for $commit == FALSE: // An associative array mapping user IDs to associative arrays with the // items "bookings" and "transactions": // { // [user-id-1]: { // "bookings" : { // [ref-1] : { ... } // }, // "transactions": [ // ] // } // } $resultData = array(); $con = Propel::getConnection(); if (!$con->beginTransaction()) { throw new Exception('Could not start transaction.'); } try { $authUser = $this->requireUser(); $authUserId = $authUser->getId(); $isAdmin = $authUser->isAdmin(); if (!$isAdmin) { throw new Exception('Non-administrative user "' . $authUser->getFQN($con) . '" cannot create transactions.'); } $clockings = ClockingAPI::createClockingQuery($authUser, $con)->joinWith('Domain.Account')->joinWith('ClockingType')->filterById($clockingIds, Criteria::IN)->addAscendingOrderByColumn(ClockingPeer::START)->addAscendingOrderByColumn(ClockingPeer::END)->addAscendingOrderByColumn(ClockingTypePeer::IDENTIFIER)->addAscendingOrderByColumn(ClockingPeer::ID)->find($con); // Check for missing clockings $missingIds = array_diff_key(array_fill_keys($clockingIds, true), $clockings->getArrayCopy('Id')); if (!empty($missingIds)) { throw new Exception('Could not find clockings with the IDs ' . implode(', ', $missingIds) . '.'); } // Lock clocking records by writing to them $this->freezeClockings(true, $clockings, $con); // Group clockings by user $clockingDataByUserId = array(); foreach ($clockings as $clocking) { $clockingDataByUserId[$clocking->getUserId()][] = EntityArray::from($clocking) + array('Type' => EntityArray::from($clocking->getClockingType())); } $typeDataByAccount = array(); foreach ($clockingDataByUserId as $userId => $userClockingData) { $user = UserQuery::create()->findPk($userId, $con); $userData = array_diff_key($user->toArray(), array('PasswordHash' => true)); $account = $user->getAccount($con); $accountId = $account->getId(); if (isset($typeDataByAccount[$accountId])) { list($typesById, $typeData) = $typeDataByAccount[$accountId]; } else { $types = BookingTypeQuery::create()->findByAccountId($account->getId(), $con); $typesById = $types->getArrayCopy('Id'); $typeData = $types->toArray('Identifier'); $typeDataByAccount[$accountId] = array($typesById, $typeData); } $data = PluginPeer::fireEvent($authUser, 'transaction', 'add', array('user' => $userData, 'clockings' => $userClockingData, 'booking_types' => $typeData, 'bookings' => array(), 'transactions' => array()), $con); if (!isset($data['bookings'])) { $data['bookings'] = array(); } if (empty($data['transactions'])) { // Ignore if there are no bookings either, otherwise fail if (!empty($data['bookings']) and is_array($data['bookings'])) { throw new Exception('Plugins created ' . count($data['bookings']) . ' booking(s) not linked to any transactions.'); } } elseif (!is_array($data['bookings']) or !is_array($data['transactions'])) { throw new Exception('Plugins must return array data in variables "bookings" and "transactions".'); } else { // Create bookings and transactions list($userClockingIds, $transactions) = $this->createBookingsTransactions($authUser, $user, $data['bookings'], $typesById, $data['transactions'], $con); $usedClockingIds += $userClockingIds; $resultData[$userId] = array('bookings' => array(), 'transactions' => EntityArray::from($transactions, $con)); } } $this->freezeClockings(false, $clockings, $con); } catch (Exception $e) { $con->rollBack(); throw $e; } if (!$commit) { $con->rollBack(); return $resultData; } if (!$con->commit()) { throw new Exception('Could not commit transaction.'); } return array_keys($usedClockingIds); }
/** * Removes a clocking entry * * @param int $id The clocking ID */ public function do_remove($id) { $con = Propel::getConnection(); if (!$con->beginTransaction()) { throw new Exception('Could not start transaction.'); } try { $authUser = $this->requireUser($con); $clocking = $this->getClockingById($id, $con); if ($clocking->getBooked()) { throw new Exception('Cannot remove clocking entry #' . $id . ' because it already has bookings. Please remove the transactions instead.'); } if (!$clocking->isOpen() and !$authUser->isAdmin()) { $account = $authUser->getAccount($con); if ($account === null) { throw new Exception('Could not get account of user #' . $authUser->getId() . ' "' . $authUser->getFQN($con) . '".'); } $type = $clocking->getClockingType($con); if ($type === null) { throw new Exception('Could not get clocking type with ID #' . $clocking->getTypeId() . '.'); } $this->validateTimeLimits($account, $authUser, $clocking, $con); } $clockingUser = $clocking->getUserRelatedByUserId($con); if ($clockingUser === null) { throw new Exception('Could not determine clocking\'s assigned user #' . $clocking->getUserId() . '.'); } PluginPeer::fireEvent($clockingUser, 'clocking', 'remove', EntityArray::from($clocking, $con), $con); $clocking->setDeleted(1)->save($con); } catch (Exception $e) { $con->rollBack(); throw $e; } if (!$con->commit()) { throw new Exception('Could not commit transaction.'); } return true; }