| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569 | <?php/** * * PHP Pro Bid $Id$ o7sbe8CfJji4DIa8b0Lloyp/chIXnE+At24rYq22Ha0= * * @link        https://www.phpprobid.com * @copyright   Copyright (c) 2017 Online Ventures Software & CodeCube SRL * @license     https://www.phpprobid.com/license Commercial License * * @version     7.10 [rev.7.10.01] *//** * sales table service class * creates/edits sales (a sale can include one ore more listings in it) */namespace Ppb\Service;use Ppb\Db\Table,    Cube\Db\Expr,    Ppb\Service,    Ppb\Service\Table\SalesListings as SalesListingsTableService,    Ppb\Model\Shipping as ShippingModel,    Ppb\Db\Table\Row\Sale as SaleModel,    Ppb\Db\Table\Row\Listing as ListingModel,    Ppb\Db\Table\Row\UserAddressBook as UserAddressBookModel;class Sales extends AbstractService{    /**     *     * the id of the sale row updated/created by a save operation     *     * @var int     */    protected $_saleId;    /**     *     * sales listings table service     *     * @var \Ppb\Service\Table\SalesListings     */    protected $_salesListings;    /**     *     * listings table service class     *     * @var \Ppb\Service\Listings     */    protected $_listings = null;    /**     *     * reputation table service class     *     * @var \Ppb\Service\Reputation     */    protected $_reputation = null;    /**     *     * class constructor     */    public function __construct()    {        parent::__construct();        $this->setTable(            new Table\Sales());    }    /**     *     * set sale id     *     * @param int $saleId     *     * @return $this     */    public function setSaleId($saleId)    {        $this->_saleId = $saleId;        return $this;    }    /**     *     * get sale id     *     * @return int     */    public function getSaleId()    {        return $this->_saleId;    }    /**     *     * get sales listings table service     *     * @return \Ppb\Service\Table\SalesListings     */    public function getSalesListings()    {        if (!$this->_salesListings instanceof SalesListingsTableService) {            $this->setSalesListings(                new SalesListingsTableService());        }        return $this->_salesListings;    }    /**     *     * set sales listings table     *     * @param \Ppb\Service\Table\SalesListings $salesListings     *     * @return \Ppb\Service\Sales     */    public function setSalesListings(SalesListingsTableService $salesListings)    {        $this->_salesListings = $salesListings;        return $this;    }    /**     *     * get listings table service class     *     * @return \Ppb\Service\Listings     */    public function getListings()    {        if (!$this->_listings instanceof Service\Listings) {            $this->setListings(                new Service\Listings());        }        return $this->_listings;    }    /**     *     * set listings table service class     *     * @param \Ppb\Service\Listings $listings     *     * @return \Ppb\Service\Sales     */    public function setListings(Service\Listings $listings)    {        $this->_listings = $listings;        return $this;    }    /**     *     * get reputation table service class     *     * @return \Ppb\Service\Reputation     */    public function getReputation()    {        if (!$this->_reputation instanceof Service\Reputation) {            $this->setReputation(                new Service\Reputation());        }        return $this->_reputation;    }    /**     *     * set reputation table service class     *     * @param \Ppb\Service\Reputation $reputation     *     * @return \Ppb\Service\Sales     */    public function setReputation(Service\Reputation $reputation)    {        $this->_reputation = $reputation;        return $this;    }    /**     *     * create or edit a sale (invoice). when creating a sale, also add rows in     * the sales listings table. when updating a sale, modify the sale id of the involved sales     * listings, and then remove any sales that do not contain any listings.     *     * calculates the postage when creating or editing the invoice     * the postage will be calculated based on the "postage_id" key, or the first option in the postage array will     * be used if the "postage_id" key doesnt correspond to a key in the results array     *     * will also add the calculated or edited insurance amount if "apply_insurance" is checked.     *     * an element from the listings array must include:     *      'listing' => object of type listing     *      'price' => the price the listing has been sold for     *      'quantity' => the quantity sold     *     * @param array $post     *     * @return \Ppb\Service\Sales     * @throws \InvalidArgumentException     */    public function save($post)    {        $sale = null;        $pending = (!empty($post['pending'])) ? $post['pending'] : 0;        if ((empty($post['buyer_id']) || empty($post['user_token'])) && empty($post['seller_id'])) {            throw new \InvalidArgumentException("The 'buyer_id'/'user_token' and 'seller_id' keys need to be specified when creating/editing a sale/shopping cart");        }        $data = $this->_prepareSaveData($post);        if (array_key_exists('id', $data)) {            $sale = $this->findBy('id', $data['id']);            unset($data['id']);        }        $newSale = false;        if (count($sale) > 0) {            $data['updated_at'] = new Expr('now()');            $sale->save($data);            $id = $sale['id'];        }        else {            $data['created_at'] = $data['updated_at'] = new Expr('now()');            $this->_table->insert($data);            $id = $this->_table->getAdapter()->lastInsertId();            $listing = $this->getListings()->findBy('id', $post['listings'][0]['listing_id']);            /** @var \Ppb\Db\Table\Row\Sale $sale */            $sale = $this->findBy('id', $id);            $sale->saveSaleData(array(                'currency'       => $listing['currency'],                'country'        => $listing['country'],                'state'          => $listing['state'],                'address'        => $listing['address'],                'pickup_options' => $listing['pickup_options'],                'apply_tax'      => $listing['apply_tax'],            ));            $newSale = true;        }        $this->setSaleId($id);        if (isset($post['listings'])) {            foreach ($post['listings'] as $data) {                $data['sale_id'] = $id;                // for shopping carts, don't allow an item to be added to a cart more than once.                if ($pending) {                    $salesListingsTable = $this->getSalesListings()->getTable();                    $select = $salesListingsTable->select('id')                        ->where("listing_id = ?", $data['listing_id'])                        ->where("sale_id = ?", $data['sale_id']);                    if (!empty($data['product_attributes'])) {                        $select->where("product_attributes = ?", $data['product_attributes']);                    }                    $saleListing = $salesListingsTable->fetchRow($select);                    if ($saleListing) {                        $data['id'] = $saleListing->getData('id');                    }                }                $this->getSalesListings()->save($data);            }        }        if (!$pending) {            $postageId = (!empty($post['postage_id'])) ? $post['postage_id'] : 0;            $applyInsurance = (!empty($post['apply_insurance'])) ? $post['apply_insurance'] : 0;            $this->_processPostageFields($sale, $post, $postageId, $applyInsurance);            if ($newSale || isset($post['checkout'])) {                $this->_processPostSaleActions();            }        }        return $this;    }    /**     *     * delete a sale (and all sale listings attached)     *     * @param int $id sale id     *     * @return int the number of affected rows     */    public function delete($id)    {        return $this->_table->delete(            $this->_table->getAdapter()->quoteInto('id = ?', $id));    }    /**     *     * if a sale is complete, do the following actions:     *     * - save sale transaction to accounting table     * - update the payer's balance if in account mode     * - prepare reputation rows for each listing in the sale     * - close any listings from the sale which had their quantity expired and update the quantity field in the listings table     * - add the tax rate to the sale if tax applies     * - set 'expires_at' field if force payment is enabled     * - email seller and buyer     * - V7.5: if sale total is 0.00 then flag_payment is set to 1     * - V7.8: if auto relist if sold is enabled: relist     *     * @return $this     */    protected function _processPostSaleActions()    {        /** @var \Ppb\Db\Table\Row\Sale $sale */        $sale = $this->findBy('id', $this->getSaleId());        $settings = $this->getSettings();        /** @var \Ppb\Db\Table\Row\User $seller */        $seller = $sale->findParentRow('\Ppb\Db\Table\Users', 'Seller');        /** @var \Ppb\Db\Table\Row\User $buyer */        $buyer = $sale->findParentRow('\Ppb\Db\Table\Users', 'Buyer');        /** @var \Ppb\Db\Table\Row\User $payer */        $payer = ($settings['sale_fee_payer'] == 'buyer') ? $buyer : $seller;        $saleTransactionService = new Service\Fees\SaleTransaction(            $sale, $payer        );        $saleTransactionFees = $saleTransactionService->calculate();        $totalAmount = $saleTransactionService->getTotalAmount();        $accountingService = new Service\Accounting();        if ($payer->userPaymentMode() == 'account') {            $payer->updateBalance(                $totalAmount);            $sale->updateActive();            $accountingService->setRefundFlag(\Ppb\Db\Table\Row\Accounting::REFUND_ALLOWED);        }        else if ($totalAmount <= 0) {            $sale->updateActive();        }        $accountingService->setUserId($payer['id'])            ->setSaleId($this->getSaleId())            ->saveMultiple($saleTransactionFees);        $reputation = $this->getReputation();        $voucher = $sale->getVoucher();        if ($voucher !== null) {            $voucher->updateUses();        }        $salesListings = $sale->findDependentRowset('\Ppb\Db\Table\SalesListings');        $listingIds = array();        $taxCalculated = false;        /** @var \Ppb\Db\Table\Row\SaleListing $saleListing */        foreach ($salesListings as $saleListing) {            /** @var \Ppb\Db\Table\Row\Listing $listing */            $listing = $saleListing->findParentRow('\Ppb\Db\Table\Listings');            // get tax rate - retrieved based on the first listing in the sale            if ($taxCalculated !== true) {                if (($taxType = $listing->getTaxType($buyer, $sale['billing_address_id'])) !== false) {                    $sale->save(array(                        'tax_rate' => $taxType->getData('amount')                    ));                }                $taxCalculated = true;            }            $quantity = $listing->updateQuantity($saleListing['quantity'], $saleListing['product_attributes'], ListingModel::SUBTRACT);            if ($quantity == 0) {                $listing->close();            }            if ($voucher !== null) {                $price = $voucher->apply($saleListing->price(), $sale['currency'], $listing['id']);                $saleListing->save(array(                    'price' => $price,                ));            }            // prepare reputation row: seller => buyer            $reputation->save(array(                'user_id'         => $sale['buyer_id'],                'poster_id'       => $sale['seller_id'],                'sale_listing_id' => $saleListing['id'],                'listing_name'    => $listing['name'],                'reputation_type' => Reputation::PURCHASE,            ));            // prepare reputation row: buyer => seller            $reputation->save(array(                'user_id'         => $sale['seller_id'],                'poster_id'       => $sale['buyer_id'],                'sale_listing_id' => $saleListing['id'],                'listing_name'    => $listing['name'],                'reputation_type' => Reputation::SALE,            ));            // relist only if the listing is closed            if ($quantity == 0) {                $listingIds[] = $listing['id'];            }        }        if (count($listingIds) > 0) {            $select = $this->getListings()->getTable()->select()                ->forUpdate()                ->where('id IN (?)', $listingIds);            $this->getListings()->fetchAll($select)                ->setAutomatic(true)                ->relist();        }        if ($seller->isForcePayment()) {            $sale->setExpiresFlag();        }        $sale->clearSalesListings();        if ($sale->calculateTotal() <= 0) {            $sale->save(array(                'flag_payment' => 1,            ));            $sale->setExpiresFlag(true);        }        $mail = new \Members\Model\Mail\User();        $mail->saleBuyerNotification($sale, $buyer)->send();        $mail->saleSellerNotification($sale, $seller)->send();        return $this;    }    /**     *     * process postage fields - calculate and save postage costs     * the shipping_address_id field will always need to be set in the sale row     * shipping needs to be enabled for this     *     *     * @param \Ppb\Db\Table\Row\Sale $sale     * @param array                  $post     * @param int                    $postageId     * @param int                    $applyInsurance     *     * @return $this     */    protected function _processPostageFields(SaleModel $sale, $post, $postageId, $applyInsurance)    {        /** @var \Ppb\Db\Table\Row\User $seller */        $seller = $sale->findParentRow('\Ppb\Db\Table\Users', 'Seller');        /** @var \Ppb\Db\Table\Row\User $buyer */        $buyer = $sale->findParentRow('\Ppb\Db\Table\Users', 'Buyer');        $shippingAddress = $buyer->getAddress($sale['shipping_address_id']);        $shippingModel = new ShippingModel($seller);        $shippingModel->setLocationId($shippingAddress['country'])            ->setPostCode($shippingAddress['zip_code']);        $salesListings = $sale->findDependentRowset('\Ppb\Db\Table\SalesListings');        /** @var \Ppb\Db\Table\Row\SaleListing $saleListing */        foreach ($salesListings as $saleListing) {            $shippingModel->addData(                $saleListing->findParentRow('\Ppb\Db\Table\Listings'), $saleListing['quantity']);        }        $result = array();        try {            $result = $shippingModel->calculatePostage();        } catch (\RuntimeException $e) {        }        $shippingDetails = (!empty($result[$postageId])) ? $result[$postageId] : reset($result);        // TODO: only the seller or admin can enter a custom amount for the postage and insurance amounts        $insuranceAmount = (isset($post['insurance_amount'])) ? $post['insurance_amount'] : $shippingModel->calculateInsurance();        $postageAmount = (isset($post['postage_amount'])) ? $post['postage_amount'] : $shippingDetails['price'];        $sale->saveSaleData(array(            'postage_id'      => $postageId,            'apply_insurance' => $applyInsurance,            'postage'         => $shippingDetails,        ));        $postageData = array(            'postage_amount'   => (double)$postageAmount,            'insurance_amount' => (double)$insuranceAmount        );        if ($shippingAddress instanceof UserAddressBookModel) {            if ($shippingAddressId = $shippingAddress->getData('id')) {                $postageData['shipping_address_id'] = $shippingAddressId;            }        }        $sale->save($postageData);        return $this;    }    /**     *     * get all carts of a certain user based on his token     *     * @var string $userToken     * @return array     */    public function getMultiOptions($userToken)    {        $data = array();        $select = $this->getTable()->select()            ->where('pending = ?', 1)            ->where('user_token = ?', $userToken)            ->order(array('updated_at DESC', 'created_at DESC'));        $rowset = $this->fetchAll($select);        /** @var \Ppb\Db\Table\Row\Sale $row */        foreach ($rowset as $row) {            /** @var \Ppb\Db\Table\Row\User $seller */            $seller = $row->findParentRow('\Ppb\Db\Table\Users', 'Seller');            $data[(string)$row['id']] = '[ ' . $row['id'] . ' ] ' .                (($seller->storeStatus(true) == true) ?                    $seller->getData('store_name') : $seller->getData('username'));        }        return $data;    }}
 |