'', // Standard '2' => 'reduced-rate', '3' => 'reduced-rate', '4' => 'zero-rate', ]; const VAT_LABELS = [ '1' => 'Standard', '2' => 'Reduced', '3' => 'Reduced', '4' => 'Zero VAT', ]; private string $assetBaseUrl = AIKANEN_S3_ASSET_URL; // Save/update imported product at WooCommerce public function upsert(array $service): string { $aikanenId = (string) ($service['id'] ?? ''); $name = $service['name'] ?? ''; $sku = $service['sku'] ?? ''; $amount = $service['amount'] ?? 0; $description = $service['description'] ?? ''; $shortDescription = $service['short_description'] ?? ''; $barcode = $service['barcode'] ?? ''; $stock = $service['stock'] ?? null; $unitCost = $service['unit_cost'] ?? null; $vatId = $service['vat_id'] ?? null; if ($aikanenId === '' || !$name) { // No Aikanen ID or name given - skip return 'skipped'; } $price = number_format((int) $amount / 100, 2, '.', ''); $discounted = $service['discounted'] ?? null; $salePrice = $discounted !== null ? number_format((int) $discounted / 100, 2, '.', '') : null; // Check if product already exists in WooCommerce $existing = get_posts([ 'post_type' => 'product', 'post_status' => ['any'], 'meta_key' => self::META_AIKANEN_ID, 'meta_value' => $aikanenId, 'numberposts' => 1, 'fields' => 'ids', ]); $productId = $existing[0] ?? null; $isNew = !$productId; if ($isNew) { // New product $product = new \WC_Product_Simple(); $product->update_meta_data(self::META_AIKANEN_ID, $aikanenId); } else { // Existing - update $product = wc_get_product($productId); if (!$product) { // No product found - skip return 'skipped'; } } $product->set_name($name); $product->set_description($description); $product->set_short_description($shortDescription); $product->set_status('publish'); $product->set_regular_price($price); $product->set_sale_price($salePrice ?? ''); $product->set_price($salePrice ?? $price); $product->set_virtual(false); $product->update_meta_data('_aikanen_last_synced', time()); if (!empty($sku)) { // If has a SKU try { $product->set_sku($sku); } catch (\WC_Data_Exception $e) { // Catch duplicate SKU return 'skipped'; } } if (!empty($barcode)) { // If has a barcode try { $product->set_global_unique_id($barcode); } catch (\WC_Data_Exception $e) { // Catch duplicate EAN return 'skipped'; } } if ($stock !== null) { // Enable stock management if data contains stock $product->set_manage_stock(true); $product->set_stock_quantity((int) $stock); $product->set_stock_status((int) $stock > 0 ? 'instock' : 'outofstock'); } else { $product->set_manage_stock(false); $product->set_stock_status('instock'); } if (wc_get_container()->get(\Automattic\WooCommerce\Internal\Features\FeaturesController::class)->feature_is_enabled('cost_of_goods_sold')) { // If cost of goods sold is enabled on WooCommerce $product->set_cogs_value($unitCost !== null ? round((int) $unitCost / 100, 2) : null); } // Resolve VAT $taxClass = $this->resolveTaxClass((string) $vatId); $product->set_tax_status('taxable'); $product->set_tax_class($taxClass); try { // Save product $product->save(); } catch (\WC_Data_Exception $e) { return 'skipped'; } if ($this->syncImage($product, $this->imageUrl($service))) { // Sync main product image if one was set in Aikanen and product doesn't yet have one in Woo $product->save(); } return $isNew ? 'created' : 'updated'; } // Match tax rate to WooCommerce tax rate private function resolveTaxClass(string $vatId): string { $vatMap = $this->getVatMap(); return $vatMap[$vatId] ?? ''; } public function getVatMap(): array { return get_option(self::OPTION_VAT_MAP, self::DEFAULT_VAT_MAP); } // Build absolute URL for a service image path from Aikanen public function imageUrl(array $service): ?string { $path = $service['image_urls']['original'] ?? null; if (!$path || !$this->assetBaseUrl) { return null; } return esc_url_raw(trailingslashit($this->assetBaseUrl) . ltrim($path, '/')); } // Add image to imported product if one was added to it on Aikanen private function syncImage(\WC_Product $product, ?string $url): bool { if (empty($url) || $product->get_image_id()) { // Nothing to import, or product already has an image return false; } require_once ABSPATH . 'wp-admin/includes/file.php'; require_once ABSPATH . 'wp-admin/includes/media.php'; require_once ABSPATH . 'wp-admin/includes/image.php'; $attachmentId = media_sideload_image($url, $product->get_id(), $product->get_name(), 'id'); if (is_wp_error($attachmentId)) { return false; } $product->set_image_id($attachmentId); return true; } // Find product matching service ID from Aikanen (if exists) public function findProductByAikanenId(string $serviceId): ?int { $posts = get_posts([ 'post_type' => 'product', 'post_status' => ['any'], 'meta_key' => self::META_AIKANEN_ID, 'meta_value' => $serviceId, 'numberposts' => 1, 'fields' => 'ids', ]); return $posts[0] ?? null; } }