private function convertPrice($price, $from) { if (!$this->currency_model) { $this->currency_model = new shopCurrencyModel(); } if (!$this->primary_currency) { $this->primary_currency = wa('shop')->getConfig()->getCurrency(); } return $this->currency_model->convert($price, $from, $this->primary_currency); }
/** Coupon discounts implementation. */ protected static function byCoupons(&$order, $contact, $apply) { $currency = isset($order['currency']) ? $order['currency'] : wa('shop')->getConfig()->getCurrency(false); $checkout_data = wa('shop')->getStorage()->read('shop/checkout'); if (empty($checkout_data['coupon_code'])) { return 0; // !!! Will this fail when recalculating existing order? } $cm = new shopCouponModel(); $coupon = $cm->getByField('code', $checkout_data['coupon_code']); if (!$coupon || !shopCouponsAction::isEnabled($coupon)) { return 0; } switch ($coupon['type']) { case '$FS': $order['shipping'] = 0; $result = 0; break; case '%': $result = max(0.0, min(100.0, (double) $coupon['value'])) * $order['total'] / 100.0; break; default: // Flat value in currency $result = max(0.0, (double) $coupon['value']); if ($currency != $coupon['type']) { $crm = new shopCurrencyModel(); $result = (double) $crm->convert($result, $coupon['type'], $currency); } break; } if ($apply) { $cm->useOne($coupon['id']); if (empty($order['params'])) { $order['params'] = array(); } $order['params']['coupon_id'] = $coupon['id']; $order['params']['coupon_discount'] = $result; } return $result; }
public function execute() { $id = waRequest::request('id', 0, 'int'); $coupm = new shopCouponModel(); $coupon = $coupm->getById($id); if ($coupon) { $coupon['value'] = (double) $coupon['value']; if (waRequest::request('delete')) { $coupm->delete($id); exit; } } else { if ($id) { throw new waException('Coupon not found.', 404); } else { // show form to create new coupon $coupon = $coupm->getEmptyRow(); $coupon['code'] = self::generateCode(); } } // // Process POST data // $duplicate_code_error = null; if (waRequest::post()) { $post_coupon = waRequest::post('coupon'); if (is_array($post_coupon)) { $post_coupon = array_intersect_key($post_coupon, $coupon) + array('code' => '', 'type' => '%'); if (empty($post_coupon['limit'])) { $post_coupon['limit'] = null; } if (!empty($post_coupon['value'])) { $post_coupon['value'] = (double) str_replace(',', '.', $post_coupon['value']); } if (empty($post_coupon['code'])) { throw new waException('Bad parameters', 500); // rely on JS validation } if (!empty($post_coupon['expire_datetime']) && strlen($post_coupon['expire_datetime']) == 10) { $post_coupon['expire_datetime'] .= ' 23:59:59'; } if ($post_coupon['type'] == '%') { $post_coupon['value'] = min(max($post_coupon['value'], 0), 100); } if ($id) { $coupm->updateById($id, $post_coupon); echo '<script>window.location.hash = "#/coupons/' . $id . '";$.orders.dispatch();</script>'; exit; } else { $post_coupon['create_contact_id'] = wa()->getUser()->getId(); $post_coupon['create_datetime'] = date('Y-m-d H:i:s'); try { $id = $coupm->insert($post_coupon); echo '<script>' . 'var counter = $("#s-coupons .count");' . 'var cnt = parseInt(counter.text(), 10) || 0;' . 'counter.text(cnt + 1);' . 'window.location.hash = "#/coupons/' . $id . '";' . '</script>'; exit; } catch (waDbException $e) { // Duplicate code. Show error in form. $coupon = $post_coupon + $coupon; $duplicate_code_error = true; } } } } // Coupon types $curm = new shopCurrencyModel(); $currencies = $curm->getAll('code'); $types = self::getTypes($currencies); // Orders this coupon was used for $orders = array(); $overall_discount = 0; $overall_discount_formatted = ''; if ($coupon['id']) { $om = new shopOrderModel(); $cm = new shopCurrencyModel(); $orders = $om->getByCoupon($coupon['id']); shopHelper::workupOrders($orders); foreach ($orders as &$o) { $discount = ifset($o['params']['coupon_discount'], 0); $o['coupon_discount_formatted'] = waCurrency::format('%{s}', $discount, $o['currency']); if ($discount) { $overall_discount += $cm->convert($discount, $o['currency'], $cm->getPrimaryCurrency()); $o['coupon_discount_percent'] = round($discount * 100.0 / ($discount + $o['total']), 1); } else { $o['coupon_discount_percent'] = 0; } } unset($o); $overall_discount_formatted = waCurrency::format('%{s}', $overall_discount, $cm->getPrimaryCurrency()); } $this->view->assign('types', $types); $this->view->assign('orders', $orders); $this->view->assign('coupon', $coupon); $this->view->assign('duplicate_code_error', $duplicate_code_error); $this->view->assign('overall_discount', $overall_discount); $this->view->assign('overall_discount_formatted', $overall_discount_formatted); $this->view->assign('formatted_value', shopCouponsAction::formatValue($coupon, $currencies)); $this->view->assign('is_enabled', shopCouponsAction::isEnabled($coupon)); }
/** * @param string $field * @param mixed $value * @param array $info * @param array $data * @param null $sku_data * @return mixed|string */ private function format($field, $value, $info = array(), $data = array(), $sku_data = null) { /** * @todo cpa field */ /** * <yml_catalog> * <shop> * <currencies> * <categories> * <local_delivery_cost> * <offers> * <picture> * <description> и <name> * <delivery>, <pickup> и <store> * <adult> * <barcode> * <cpa> TODO * <rec> * <param> (name,unit,value) * <vendor> */ static $currency_model; static $size; switch ($field) { case 'group_id': if ($value === 'auto') { if (!empty($data['market_category'])) { $value = $this->plugin()->isGroupedCategory($data['market_category']) ? $data['id'] : null; } else { $info['format'] = false; } } break; case 'market_category': //it's product constant field //TODO verify it break; case 'name': if (!empty($sku_data['name']) && !empty($data['name']) && $sku_data['name'] != $data['name']) { $value = sprintf('%s (%s)', $value, $sku_data['name']); } $value = preg_replace('/<br\\/?\\s*>/', "\n", $value); $value = preg_replace("/[\r\n]+/", "\n", $value); $value = strip_tags($value); $value = trim($value); if (mb_strlen($value) > 255) { $value = mb_substr($value, 0, 252) . '...'; } break; case 'description': $value = preg_replace('/<br\\/?\\s*>/', "\n", $value); $value = preg_replace("/[\r\n]+/", "\n", $value); $value = strip_tags($value); $value = trim($value); if (mb_strlen($value) > 512) { $value = mb_substr($value, 0, 509) . '...'; } break; case 'barcode': //может содержать несколько элементов $value = preg_replace('@\\D+@', '', $value); if (!in_array(strlen($value), array(8, 12, 13))) { $value = null; } break; case 'sales_notes': $value = trim($value); if (mb_strlen($value) > 50) { $value = mb_substr($value, 0, 50); } break; case 'typePrefix': $model = new shopTypeModel(); if ($type = $model->getById($value)) { $value = $type['name']; } break; case 'url': //max 512 $value = preg_replace_callback('@([^\\[\\]a-zA-Z\\d_/-\\?=%&,\\.]+)@i', array(__CLASS__, 'rawurlencode'), $value); if ($this->data['utm']) { $value .= (strpos($value, '?') ? '&' : '?') . $this->data['utm']; } $value = 'http://' . ifempty($this->data['base_url'], 'localhost') . $value; break; case 'oldprice': if (empty($value) || empty($this->data['export']['compare_price'])) { $value = null; break; } case 'price': if (!$currency_model) { $currency_model = new shopCurrencyModel(); } if ($sku_data) { if (!in_array($data['currency'], $this->data['currency'])) { $value = $currency_model->convert($value, $data['currency'], $this->data['primary_currency']); $data['currency'] = $this->data['primary_currency']; } } else { if (!in_array($data['currency'], $this->data['currency'])) { #value in default currency if ($this->data['default_currency'] != $this->data['primary_currency']) { $value = $currency_model->convert($value, $this->data['default_currency'], $this->data['primary_currency']); } $data['currency'] = $this->data['primary_currency']; } elseif ($this->data['default_currency'] != $data['currency']) { $value = $currency_model->convert($value, $this->data['default_currency'], $data['currency']); } } break; case 'currencyId': if (!in_array($value, $this->data['currency'])) { $value = $this->data['primary_currency']; } break; case 'rate': if (!in_array($value, array('CB', 'CBRF', 'NBU', 'NBK'))) { $info['format'] = '%0.4f'; } break; case 'available': if (!empty($sku_data) && isset($sku_data['available']) && empty($sku_data['available'])) { $value = 'false'; } if (is_object($value)) { switch (get_class($value)) { case 'shopBooleanValue': /** * @var $value shopBooleanValue */ $value = $value->value ? 'true' : 'false'; break; } } $value = ($value <= 0 || $value === 'false' || empty($value)) && $value !== null && $value !== 'true' ? 'false' : 'true'; break; case 'store': case 'pickup': case 'delivery': case 'adult ': if (is_object($value)) { switch (get_class($value)) { case 'shopBooleanValue': /** * @var $value shopBooleanValue */ $value = $value->value ? 'true' : 'false'; break; } } $value = empty($value) || $value === 'false' ? 'false' : 'true'; break; case 'picture': //max 512 $values = array(); $limit = 10; if (!empty($sku_data['image_id'])) { $value = array(ifempty($value[$sku_data['image_id']])); } while (is_array($value) && ($image = array_shift($value)) && $limit--) { if (!$size) { $shop_config = wa('shop')->getConfig(); /** * @var $shop_config shopConfig */ $size = $shop_config->getImageSize('big'); } $values[] = 'http://' . ifempty($this->data['base_url'], 'localhost') . shopImage::getUrl($image, $size); } $value = $values; break; case 'page_extent': $value = max(1, intval($value)); break; case 'seller_warranty': case 'manufacturer_warranty': case 'expiry': /** * ISO 8601, например: P1Y2M10DT2H30M */ $pattern = '@P((\\d+S)?(\\d+M)(\\d+D)?)?(T(\\d+H)?(\\d+M)(\\d+S)?)?@'; $class = is_object($value) ? get_class($value) : false; switch ($class) { case 'shopBooleanValue': /** * @var $value shopBooleanValue */ $value = $value->value ? 'true' : 'false'; break; case 'shopDimensionValue': /** * @var $value shopDimensionValue */ $value = $value->convert('s', false); /** * @var $value int */ if (empty($value)) { $value = 'false'; } else { $value = $this->formatCustom($value, 'ISO8601'); } break; default: $value = (string) $value; if (empty($value) || $value == 'false') { $value = 'false'; } elseif (preg_match('@^\\d+$@', trim($value))) { $value = $this->formatCustom(intval($value) * 3600 * 24, 'ISO8601'); } elseif (!preg_match($pattern, $value)) { $value = 'true'; } break; } break; case 'year': if (empty($value)) { $value = null; } break; case 'ISBN': /** * @todo verify format * Код книги, если их несколько, то указываются через запятую. * Форматы ISBN и SBN проверяются на корректность. Валидация кодов происходит не только по длине, * также проверяется контрольная цифра (check-digit) – последняя цифра кода должна согласовываться * с остальными цифрами по определенной формуле. При разбиении ISBN на части при помощи дефиса * (например, 978-5-94878-004-7) код проверяется на соответствие дополнительным требованиям к * количеству цифр в каждой из частей. * Необязательный элемент. **/ break; case 'recording_length': /** * Время звучания задается в формате mm.ss (минуты.секунды). **/ if (is_object($value)) { switch (get_class($value)) { case 'shopDimensionValue': /** * @var $value shopDimensionValue */ $value = $value->convert('s', false); break; default: $value = (int) $value; break; } } $value = sprintf('%02d.%02d', floor($value / 60), $value % 60); break; case 'weight': /** * Элемент предназначен для указания веса товара. Вес указывается в килограммах с учетом упаковки. * Формат элемента: положительное число с точностью 0.001, разделитель целой и дробной части — точка. * При указании более высокой точности значение автоматически округляется следующим способом: * — если 4-ый знак после разделителя меньше 5, то 3-й знак сохраняется, а все последующие обнуляются; * — если 4-ый знак после разделителя больше или равен 5, то 3-й знак увеличивается на единицу, а все последующие обнуляются. **/ if (is_object($value)) { switch (get_class($value)) { case 'shopDimensionValue': /** * @var $value shopDimensionValue */ if ($value->type == 'weight') { $value = $value->convert('kg', '%0.3f'); } break; default: $value = floatval($value); break; } } else { $value = floatval($value); } break; case 'dimensions': /** * * Элемент предназначен для указания габаритов товара (длина, ширина, высота) в упаковке. Размеры указываются в сантиметрах. * Формат элемента: три положительных числа с точностью 0.001, разделитель целой и дробной части — точка. Числа должны быть разделены символом «/» без пробелов. * При указании более высокой точности значение автоматически округляется следующим способом: * — если 4-ый знак после разделителя меньше 5, то 3-й знак сохраняется, а все последующие обнуляются; * — если 4-ый знак после разделителя больше или равен 5, то 3-й знак увеличивается на единицу, а все последующие обнуляются. **/ /** * @todo use cm * */ $parsed_value = array(); $class = is_object($value) ? get_class($value) : false; switch ($class) { case 'shopCompositeValue': /** * @var $value shopCompositeValue */ for ($i = 0; $i < 3; $i++) { $value_item = $value[$i]; $class_item = is_object($value_item) ? get_class($value_item) : false; switch ($class_item) { case 'shopDimensionValue': /** * @var $value_item shopDimensionValue */ if ($value_item->type == '3d.length') { $parsed_value[] = $value_item->convert('cm', '%0.4f'); } else { $parsed_value[] = sprintf('%0.4f', (string) $value_item); } break; default: $parsed_value[] = sprintf('%0.4f', (string) $value_item); break; } } break; default: $parsed_value = array_map('floatval', explode(':', preg_replace('@[^\\d\\.,]+@', ':', $value), 3)); break; } foreach ($parsed_value as &$p) { $p = str_replace(',', '.', sprintf('%0.4f', $p)); unset($p); } $value = implode('/', $parsed_value); break; case 'age': /** * @todo * unit="year": 0, 6, 12, 16, 18 * unit="month": 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 */ if (is_object($value)) { switch (get_class($value)) { case 'shopDimensionValue': /** * @var $value shopDimensionValue */ if ($value->type == 'time') { $value = $value->convert('month', false); } break; default: $value = intval($value); break; } } else { /** * @var $value shopDimensionValue */ if (preg_match('@^(year|month)s?:(\\d+)$@', trim($value), $matches)) { $value = array('unit' => $matches[1], 'value' => intval($matches[2])); } else { $value = intval($value); } } if (!is_array($value)) { if ($value > 12) { $value = array('unit' => 'year', 'value' => floor($value / 12)); } else { $value = array('unit' => 'month', 'value' => intval($value)); } } break; case 'country_of_origin': /** * @todo * @see http://partner.market.yandex.ru/pages/help/Countries.pdf */ break; case 'local_delivery_cost': if ($value !== '') { $value = max(0, floatval($value)); } break; case 'days': $value = max(1, intval($value)); break; case 'dataTour': /** * @todo * Даты заездов. * Необязательный элемент. Элемент <offer> может содержать несколько элементов <dataTour>. **/ break; case 'hotel_stars': /** * @todo * Звезды отеля. * Необязательный элемент. **/ break; case 'room': /** * @todo * Тип комнаты (SNG, DBL, ...). * Необязательный элемент. **/ break; case 'meal': /** * @todo * Тип питания (All, HB, ...). * Необязательный элемент. **/ break; case 'date': /** * @todo * Дата и время сеанса. Указываются в формате ISO 8601: YYYY-MM-DDThh:mm. **/ break; case 'hall': /** * @todo * max 512 * Ссылка на изображение с планом зала. **/ //plan - property break; case 'param': $unit = null; $name = ifset($info['source_name'], ''); if ($value instanceof shopDimensionValue) { $unit = $value->unit_name; $value = $value->format('%s'); } elseif (is_array($value)) { $_value = reset($value); if ($_value instanceof shopDimensionValue) { $unit = $_value->unit_name; $values = array(); foreach ($value as $_value) { /** * @var shopDimensionValue $_value */ $values[] = $_value->convert($unit, '%s'); } $value = implode(', ', $values); } else { if (preg_match('@^(.+)\\s*\\(([^\\)]+)\\)\\s*$@', $name, $matches)) { //feature name based unit $unit = $matches[2]; $name = $matches[1]; } $value = implode(', ', $value); } } elseif (preg_match('@^(.+)\\s*\\(([^\\)]+)\\)\\s*$@', $name, $matches)) { //feature name based unit $unit = $matches[2]; $name = $matches[1]; } $value = trim((string) $value); if (in_array($value, array(null, false, ''), true)) { $value = null; } else { $value = array('name' => $name, 'unit' => $unit, 'value' => trim((string) $value)); } break; } $format = ifempty($info['format'], '%s'); if (is_array($value)) { /** * @var $value array */ reset($value); if (key($value) == 0) { foreach ($value as &$item) { $item = str_replace(' ', ' ', $item); $item = str_replace('&', '&', $item); $item = $this->sprintf($format, $item); } unset($item); } if (!in_array($field, array('email', 'picture', 'dataTour', 'additional', 'barcode', 'param', 'related_offer'))) { $value = implode(', ', $value); } } elseif ($value !== null) { /** * @var $value string */ $value = str_replace(' ', ' ', $value); $value = str_replace('&', '&', $value); $value = $this->sprintf($format, $value); } return $value; }
public static function discount(&$order, $contact, $apply, $other_discounts) { if (!$contact || !$contact->getId()) { return 0; } $checkout_data = wa()->getStorage()->read('shop/checkout'); if (empty($checkout_data['use_affiliate'])) { return 0; // !!! Will this fail when recalculating existing order? } $usage_rate = (double) wa()->getSetting('affiliate_usage_rate', 0, 'shop'); if ($usage_rate <= 0) { return 0; } $cm = new shopCustomerModel(); $customer = $cm->getById($contact->getId()); if (!$customer || $customer['affiliate_bonus'] <= 0) { return 0; } $order_total = $order['total'] - $other_discounts; $max_bonus = $customer['affiliate_bonus']; if (!empty($order['params']['affiliate_bonus'])) { // Recalculating existing order: take old discount into account $max_bonus += $order['params']['affiliate_bonus']; } $crm = new shopCurrencyModel(); $discount = (double) $crm->convert($max_bonus * $usage_rate, wa()->getConfig()->getCurrency(true), wa()->getConfig()->getCurrency(false)); if ($discount > $order_total) { $discount = $order_total; } if ($discount < $order_total) { $bonus_used = $max_bonus; } else { $bonus_used = (double) $crm->convert($discount, wa()->getConfig()->getCurrency(false), wa()->getConfig()->getCurrency(true)) / $usage_rate; } if (empty($order['params'])) { $order['params'] = array(); } $order['params']['affiliate_bonus'] = $bonus_used; if ($apply) { $balance_change = $max_bonus - $bonus_used - $customer['affiliate_bonus']; if (abs($balance_change) > 0.0001) { if (!empty($order['params']['affiliate_bonus'])) { $message = sprintf_wp('Recalculation of order total, new discount: %s', waCurrency::format('%{s}', $discount, wa()->getConfig()->getCurrency())); } else { $message = sprintf_wp('Discount of %s', waCurrency::format('%{s}', $discount, wa()->getConfig()->getCurrency())); } $atm = new shopAffiliateTransactionModel(); $atm->applyBonus($contact->getId(), $balance_change, ifset($order['id']), $message); } } return $discount; }
/** * @param int $id * @return bool */ public function correct($id) { if (!$id) { return false; } $id = (int) $id; $product = $this->getById($id); $product_skus_model = new shopProductSkusModel(); $skus = $product_skus_model->getDataByProductId($id, true); $currency_model = new shopCurrencyModel(); $currency = wa('shop')->getConfig()->getCurrency(); $price = array(); $update_product_data = array(); // aggregate count by stocks for product // Invariant: if at least one sku.count IS NULL this aggregate count IS NULL $product_count = 0; $available_sku_count = 0; foreach ($skus as $sku) { if ($sku['available']) { $available_sku_count++; } $price[] = $this->castValue('double', $sku['price']); $sku_count = 0; $num_of_null = 0; foreach ($sku['stock'] as $count) { if ($count === null) { // turn into NULL and is not longer changing $sku_count = null; $num_of_null++; } else { // Once turned into NULL value is not changed if ($sku_count !== null) { $sku_count += $count; } } } if ($num_of_null == count($sku['stock'])) { // all stock count is null means that not multistocking $sku_count = $sku['count']; } // maintain product_count invariant. See above if ($sku['available']) { if ($sku_count === null) { $product_count = null; } elseif ($product_count !== null) { $product_count += $sku_count; } } } if ($available_sku_count == 0) { $product_count = 0; } if (!$price) { $price[] = 0; } $update_product_data['sku_count'] = count($skus); $update_product_data['min_price'] = $currency_model->convert(min($price), $product['currency'], $currency); $update_product_data['max_price'] = $currency_model->convert(max($price), $product['currency'], $currency); $update_product_data['price'] = $currency_model->convert($skus[$product['sku_id']]['price'], $product['currency'], $currency); if (isset($skus[$product['sku_id']]['compare_price'])) { $update_product_data['compare_price'] = $currency_model->convert($skus[$product['sku_id']]['compare_price'], $product['currency'], $currency); } $update_product_data['count'] = $product_count; $this->updateById($product['id'], $update_product_data); return true; }