|
|
@@ -21,6 +21,16 @@ class ModelExtensionModuleReverb extends Model {
|
|
|
");
|
|
|
$this->migrate();
|
|
|
|
|
|
+ $this->db->query("
|
|
|
+ CREATE TABLE IF NOT EXISTS `" . DB_PREFIX . "reverb_order_map` (
|
|
|
+ `reverb_order_number` VARCHAR(64) NOT NULL,
|
|
|
+ `order_id` INT(11) NOT NULL DEFAULT 0,
|
|
|
+ `reverb_status` VARCHAR(32) NOT NULL DEFAULT '',
|
|
|
+ `created_at` DATETIME NOT NULL,
|
|
|
+ PRIMARY KEY (`reverb_order_number`)
|
|
|
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
|
|
|
+ ");
|
|
|
+
|
|
|
$this->db->query("
|
|
|
CREATE TABLE IF NOT EXISTS `" . DB_PREFIX . "reverb_sync_log` (
|
|
|
`log_id` INT(11) NOT NULL AUTO_INCREMENT,
|
|
|
@@ -39,6 +49,7 @@ class ModelExtensionModuleReverb extends Model {
|
|
|
public function uninstall() {
|
|
|
$this->db->query("DROP TABLE IF EXISTS `" . DB_PREFIX . "reverb_product_map`");
|
|
|
$this->db->query("DROP TABLE IF EXISTS `" . DB_PREFIX . "reverb_sync_log`");
|
|
|
+ $this->db->query("DROP TABLE IF EXISTS `" . DB_PREFIX . "reverb_order_map`");
|
|
|
}
|
|
|
|
|
|
public function migrate() {
|
|
|
@@ -47,6 +58,16 @@ class ModelExtensionModuleReverb extends Model {
|
|
|
$done = true;
|
|
|
$this->addColumnIfMissing(DB_PREFIX . 'reverb_product_map', 'handmade', 'TINYINT(1) NOT NULL DEFAULT 0');
|
|
|
$this->addColumnIfMissing(DB_PREFIX . 'reverb_product_map', 'upc_does_not_apply', 'TINYINT(1) NOT NULL DEFAULT 1');
|
|
|
+ // Create order map table for upgrades from earlier versions
|
|
|
+ $this->db->query("
|
|
|
+ CREATE TABLE IF NOT EXISTS `" . DB_PREFIX . "reverb_order_map` (
|
|
|
+ `reverb_order_number` VARCHAR(64) NOT NULL,
|
|
|
+ `order_id` INT(11) NOT NULL DEFAULT 0,
|
|
|
+ `reverb_status` VARCHAR(32) NOT NULL DEFAULT '',
|
|
|
+ `created_at` DATETIME NOT NULL,
|
|
|
+ PRIMARY KEY (`reverb_order_number`)
|
|
|
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci
|
|
|
+ ");
|
|
|
}
|
|
|
|
|
|
private function addColumnIfMissing($table, $column, $definition) {
|
|
|
@@ -251,6 +272,27 @@ class ModelExtensionModuleReverb extends Model {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Return Reverb categories grouped by their root (top-level) category name.
|
|
|
+ * Parses the full_name field (e.g. "Guitars > Electric Guitars > Solidbody").
|
|
|
+ *
|
|
|
+ * @return array ['Root Name' => [['uuid'=>..., 'full_name'=>..., 'name'=>...], ...], ...]
|
|
|
+ */
|
|
|
+ public function getReverbCategoriesGrouped() {
|
|
|
+ $flat = $this->getReverbCategories();
|
|
|
+ $grouped = [];
|
|
|
+
|
|
|
+ foreach ($flat as $cat) {
|
|
|
+ $full = $cat['full_name'] ?? $cat['name'] ?? '';
|
|
|
+ $parts = array_map('trim', explode('>', $full));
|
|
|
+ $root = $parts[0] ?: 'Other';
|
|
|
+ $grouped[$root][] = $cat;
|
|
|
+ }
|
|
|
+
|
|
|
+ ksort($grouped);
|
|
|
+ return $grouped;
|
|
|
+ }
|
|
|
+
|
|
|
// -------------------------------------------------------------------------
|
|
|
// Sync log
|
|
|
// -------------------------------------------------------------------------
|
|
|
@@ -340,6 +382,242 @@ class ModelExtensionModuleReverb extends Model {
|
|
|
return $listing_id;
|
|
|
}
|
|
|
|
|
|
+ // -------------------------------------------------------------------------
|
|
|
+ // Order import (Reverb → OpenCart)
|
|
|
+ // -------------------------------------------------------------------------
|
|
|
+
|
|
|
+ public function importOrdersFromReverb(array $settings) {
|
|
|
+ require_once(DIR_SYSTEM . 'library/reverb/ReverbApi.php');
|
|
|
+ $this->migrate();
|
|
|
+
|
|
|
+ $api = $this->getApi();
|
|
|
+ $last_sync = $this->config->get('module_reverb_order_last_sync');
|
|
|
+ $imported = 0;
|
|
|
+ $skipped = 0;
|
|
|
+ $page = 1;
|
|
|
+
|
|
|
+ do {
|
|
|
+ $response = $api->getOrders($page, 50, $last_sync ?: null);
|
|
|
+ $orders = $response['orders'] ?? [];
|
|
|
+ $total_pages = (int)($response['total_pages'] ?? 1);
|
|
|
+
|
|
|
+ foreach ($orders as $reverb_order) {
|
|
|
+ $order_number = $reverb_order['order_number'] ?? null;
|
|
|
+ if (!$order_number) { $skipped++; continue; }
|
|
|
+
|
|
|
+ // Skip already-imported orders
|
|
|
+ $existing = $this->db->query("
|
|
|
+ SELECT order_id FROM `" . DB_PREFIX . "reverb_order_map`
|
|
|
+ WHERE `reverb_order_number` = '" . $this->db->escape($order_number) . "'
|
|
|
+ ");
|
|
|
+ if ($existing->num_rows) { $skipped++; continue; }
|
|
|
+
|
|
|
+ // Skip cancelled/unpaid
|
|
|
+ $status = $reverb_order['status'] ?? '';
|
|
|
+ if (in_array($status, ['cancelled', 'blocked', 'unpaid', 'payment_pending', 'pending_review'])) {
|
|
|
+ $skipped++; continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ $order_id = $this->createOcOrderFromReverb($reverb_order, $settings);
|
|
|
+ if ($order_id) {
|
|
|
+ $this->db->query("
|
|
|
+ INSERT INTO `" . DB_PREFIX . "reverb_order_map`
|
|
|
+ (`reverb_order_number`, `order_id`, `reverb_status`, `created_at`)
|
|
|
+ VALUES (
|
|
|
+ '" . $this->db->escape($order_number) . "',
|
|
|
+ '" . (int)$order_id . "',
|
|
|
+ '" . $this->db->escape($status) . "',
|
|
|
+ NOW()
|
|
|
+ )
|
|
|
+ ");
|
|
|
+ $this->log(0, 'pull', 'success', 'Imported Reverb order #' . $order_number . ' → OC order #' . $order_id);
|
|
|
+ $imported++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $page++;
|
|
|
+ } while ($page <= $total_pages);
|
|
|
+
|
|
|
+ // Save timestamp so next import only fetches newer orders
|
|
|
+ $this->load->model('setting/setting');
|
|
|
+ $this->model_setting_setting->editSettingValue('module_reverb', 'module_reverb_order_last_sync', date('c'));
|
|
|
+
|
|
|
+ return ['imported' => $imported, 'skipped' => $skipped];
|
|
|
+ }
|
|
|
+
|
|
|
+ private function createOcOrderFromReverb(array $o, array $settings) {
|
|
|
+ // Buyer name
|
|
|
+ $buyer_name = trim($o['buyer_name'] ?? 'Reverb Buyer');
|
|
|
+ $np = explode(' ', $buyer_name, 2);
|
|
|
+ $firstname = $np[0];
|
|
|
+ $lastname = $np[1] ?? '';
|
|
|
+
|
|
|
+ // Shipping address
|
|
|
+ $addr = $o['shipping_address'] ?? [];
|
|
|
+ $anp = explode(' ', trim($addr['name'] ?? $buyer_name), 2);
|
|
|
+ $ship_first = $anp[0];
|
|
|
+ $ship_last = $anp[1] ?? '';
|
|
|
+
|
|
|
+ // Amounts
|
|
|
+ $product_amt = (float)($o['amount_product']['amount'] ?? $o['amount_product'] ?? 0);
|
|
|
+ $shipping_amt = (float)($o['shipping']['amount'] ?? $o['shipping'] ?? 0);
|
|
|
+ $total_amt = (float)($o['total']['amount'] ?? $o['total'] ?? ($product_amt + $shipping_amt));
|
|
|
+ $currency = $o['total']['currency'] ?? $settings['currency'] ?? 'AUD';
|
|
|
+ $qty = (int)($o['quantity'] ?? 1);
|
|
|
+
|
|
|
+ // OC order status mapping
|
|
|
+ $status_map = ['paid' => 2, 'shipped' => 3, 'received' => 5, 'picked_up' => 5];
|
|
|
+ $status_id = $status_map[$o['status'] ?? ''] ?? 1;
|
|
|
+
|
|
|
+ // Match OC product by Reverb listing ID or SKU
|
|
|
+ $listing_id = (string)($o['listing_id'] ?? $o['product_id'] ?? '');
|
|
|
+ $sku = $o['sku'] ?? '';
|
|
|
+ $title = $o['title'] ?? 'Reverb Item';
|
|
|
+
|
|
|
+ $oc_product = ($listing_id ? $this->findOcProductByListingId($listing_id) : null)
|
|
|
+ ?: ($sku ? $this->findOcProductBySku($sku) : null);
|
|
|
+
|
|
|
+ $product_id = $oc_product ? (int)$oc_product['product_id'] : 0;
|
|
|
+ $product_name = $oc_product ? $oc_product['name'] : $title;
|
|
|
+ $product_model = $oc_product ? $oc_product['model'] : $sku;
|
|
|
+
|
|
|
+ // Currency ID from OC DB
|
|
|
+ $cq = $this->db->query("SELECT currency_id FROM `" . DB_PREFIX . "currency` WHERE code = '" . $this->db->escape($currency) . "' LIMIT 1");
|
|
|
+ $currency_id = $cq->num_rows ? (int)$cq->row['currency_id'] : 1;
|
|
|
+
|
|
|
+ $date_added = date('Y-m-d H:i:s', strtotime($o['paid_at'] ?? $o['created_at'] ?? 'now'));
|
|
|
+ $store_name = $this->db->escape($this->config->get('config_name') ?? '');
|
|
|
+ $store_url = $this->db->escape($settings['store_url'] ?? '');
|
|
|
+ $lang_id = (int)$this->config->get('config_language_id');
|
|
|
+ $comment = $this->db->escape('Reverb order #' . ($o['order_number'] ?? ''));
|
|
|
+
|
|
|
+ $this->db->query("
|
|
|
+ INSERT INTO `" . DB_PREFIX . "order` SET
|
|
|
+ `invoice_prefix` = 'REV-',
|
|
|
+ `invoice_no` = 0,
|
|
|
+ `store_id` = 0,
|
|
|
+ `store_name` = '$store_name',
|
|
|
+ `store_url` = '$store_url',
|
|
|
+ `customer_id` = 0,
|
|
|
+ `customer_group_id` = 1,
|
|
|
+ `firstname` = '" . $this->db->escape($firstname) . "',
|
|
|
+ `lastname` = '" . $this->db->escape($lastname) . "',
|
|
|
+ `email` = '" . $this->db->escape($o['buyer_id'] ?? '') . "',
|
|
|
+ `telephone` = '" . $this->db->escape($addr['phone'] ?? '') . "',
|
|
|
+ `fax` = '',
|
|
|
+ `custom_field` = '{}',
|
|
|
+ `payment_firstname` = '" . $this->db->escape($ship_first) . "',
|
|
|
+ `payment_lastname` = '" . $this->db->escape($ship_last) . "',
|
|
|
+ `payment_company` = '',
|
|
|
+ `payment_address_1` = '" . $this->db->escape($addr['street_address'] ?? '') . "',
|
|
|
+ `payment_address_2` = '" . $this->db->escape($addr['extended_address'] ?? '') . "',
|
|
|
+ `payment_city` = '" . $this->db->escape($addr['locality'] ?? '') . "',
|
|
|
+ `payment_postcode` = '" . $this->db->escape($addr['postal_code'] ?? '') . "',
|
|
|
+ `payment_country` = '" . $this->db->escape($addr['country_code'] ?? '') . "',
|
|
|
+ `payment_country_id` = 0,
|
|
|
+ `payment_zone` = '" . $this->db->escape($addr['region'] ?? '') . "',
|
|
|
+ `payment_zone_id` = 0,
|
|
|
+ `payment_address_format` = '',
|
|
|
+ `payment_custom_field` = '{}',
|
|
|
+ `payment_method` = 'Reverb',
|
|
|
+ `payment_code` = 'reverb',
|
|
|
+ `shipping_firstname` = '" . $this->db->escape($ship_first) . "',
|
|
|
+ `shipping_lastname` = '" . $this->db->escape($ship_last) . "',
|
|
|
+ `shipping_company` = '',
|
|
|
+ `shipping_address_1` = '" . $this->db->escape($addr['street_address'] ?? '') . "',
|
|
|
+ `shipping_address_2` = '" . $this->db->escape($addr['extended_address'] ?? '') . "',
|
|
|
+ `shipping_city` = '" . $this->db->escape($addr['locality'] ?? '') . "',
|
|
|
+ `shipping_postcode` = '" . $this->db->escape($addr['postal_code'] ?? '') . "',
|
|
|
+ `shipping_country` = '" . $this->db->escape($addr['country_code'] ?? '') . "',
|
|
|
+ `shipping_country_id` = 0,
|
|
|
+ `shipping_zone` = '" . $this->db->escape($addr['region'] ?? '') . "',
|
|
|
+ `shipping_zone_id` = 0,
|
|
|
+ `shipping_address_format` = '',
|
|
|
+ `shipping_custom_field` = '{}',
|
|
|
+ `shipping_method` = 'Reverb',
|
|
|
+ `shipping_code` = 'reverb.reverb',
|
|
|
+ `comment` = '$comment',
|
|
|
+ `total` = '" . number_format($total_amt, 4, '.', '') . "',
|
|
|
+ `order_status_id` = $status_id,
|
|
|
+ `affiliate_id` = 0,
|
|
|
+ `commission` = '0.0000',
|
|
|
+ `marketing_id` = 0,
|
|
|
+ `tracking` = '',
|
|
|
+ `language_id` = $lang_id,
|
|
|
+ `currency_id` = $currency_id,
|
|
|
+ `currency_code` = '" . $this->db->escape($currency) . "',
|
|
|
+ `currency_value` = '1.00000000',
|
|
|
+ `ip` = '',
|
|
|
+ `forwarded_ip` = '',
|
|
|
+ `user_agent` = 'Reverb Import',
|
|
|
+ `accept_language` = '',
|
|
|
+ `date_added` = '" . $this->db->escape($date_added) . "',
|
|
|
+ `date_modified` = NOW()
|
|
|
+ ");
|
|
|
+
|
|
|
+ $order_id = (int)$this->db->getLastId();
|
|
|
+ if (!$order_id) return null;
|
|
|
+
|
|
|
+ // Order product
|
|
|
+ $unit_price = $qty > 0 ? $product_amt / $qty : $product_amt;
|
|
|
+ $this->db->query("
|
|
|
+ INSERT INTO `" . DB_PREFIX . "order_product` SET
|
|
|
+ `order_id` = $order_id,
|
|
|
+ `product_id` = $product_id,
|
|
|
+ `master_id` = 0,
|
|
|
+ `name` = '" . $this->db->escape($product_name) . "',
|
|
|
+ `model` = '" . $this->db->escape($product_model) . "',
|
|
|
+ `quantity` = $qty,
|
|
|
+ `price` = '" . number_format($unit_price, 4, '.', '') . "',
|
|
|
+ `total` = '" . number_format($product_amt, 4, '.', '') . "',
|
|
|
+ `tax` = '0.0000',
|
|
|
+ `reward` = 0
|
|
|
+ ");
|
|
|
+
|
|
|
+ // Totals
|
|
|
+ $this->db->query("INSERT INTO `" . DB_PREFIX . "order_total` SET `order_id`=$order_id, `code`='sub_total', `title`='Sub-Total', `value`='" . number_format($product_amt, 4, '.', '') . "', `sort_order`=1");
|
|
|
+ if ($shipping_amt > 0) {
|
|
|
+ $this->db->query("INSERT INTO `" . DB_PREFIX . "order_total` SET `order_id`=$order_id, `code`='shipping', `title`='Shipping', `value`='" . number_format($shipping_amt, 4, '.', '') . "', `sort_order`=3");
|
|
|
+ }
|
|
|
+ $this->db->query("INSERT INTO `" . DB_PREFIX . "order_total` SET `order_id`=$order_id, `code`='total', `title`='Total', `value`='" . number_format($total_amt, 4, '.', '') . "', `sort_order`=9");
|
|
|
+
|
|
|
+ // History
|
|
|
+ $this->db->query("INSERT INTO `" . DB_PREFIX . "order_history` SET `order_id`=$order_id, `order_status_id`=$status_id, `notify`=0, `comment`='Imported from Reverb', `date_added`=NOW()");
|
|
|
+
|
|
|
+ // Decrement OC stock
|
|
|
+ if ($product_id) {
|
|
|
+ $this->db->query("UPDATE `" . DB_PREFIX . "product` SET `quantity`=GREATEST(0,`quantity`-$qty) WHERE `product_id`=$product_id");
|
|
|
+ }
|
|
|
+
|
|
|
+ return $order_id;
|
|
|
+ }
|
|
|
+
|
|
|
+ private function findOcProductByListingId($listing_id) {
|
|
|
+ if (!$listing_id) return null;
|
|
|
+ $q = $this->db->query("
|
|
|
+ SELECT p.product_id, pd.name, p.model, p.price
|
|
|
+ FROM `" . DB_PREFIX . "reverb_product_map` r
|
|
|
+ JOIN `" . DB_PREFIX . "product` p ON p.product_id = r.product_id
|
|
|
+ JOIN `" . DB_PREFIX . "product_description` pd ON pd.product_id = p.product_id
|
|
|
+ AND pd.language_id = '" . (int)$this->config->get('config_language_id') . "'
|
|
|
+ WHERE r.reverb_listing_id = '" . $this->db->escape($listing_id) . "'
|
|
|
+ LIMIT 1
|
|
|
+ ");
|
|
|
+ return $q->num_rows ? $q->row : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ private function findOcProductBySku($sku) {
|
|
|
+ if (!$sku) return null;
|
|
|
+ $q = $this->db->query("
|
|
|
+ SELECT p.product_id, pd.name, p.model, p.price
|
|
|
+ FROM `" . DB_PREFIX . "product` p
|
|
|
+ JOIN `" . DB_PREFIX . "product_description` pd ON pd.product_id = p.product_id
|
|
|
+ AND pd.language_id = '" . (int)$this->config->get('config_language_id') . "'
|
|
|
+ WHERE p.model = '" . $this->db->escape($sku) . "'
|
|
|
+ LIMIT 1
|
|
|
+ ");
|
|
|
+ return $q->num_rows ? $q->row : null;
|
|
|
+ }
|
|
|
+
|
|
|
// -------------------------------------------------------------------------
|
|
|
// Utility
|
|
|
// -------------------------------------------------------------------------
|