$lineItems */ public function build(array $lineItems): string { if (empty($lineItems)) { return ''; } // Group items by bundle_group (null = standalone) $groups = []; $standalone = []; foreach ($lineItems as $item) { $bundleGroup = $item['bundle_group'] ?? null; if ($bundleGroup !== null) { $groups[$bundleGroup][] = $item; } else { $standalone[] = $item; } } $skuParts = []; // Build bundles (pipe-separated) foreach ($groups as $groupItems) { $bundleParts = []; foreach ($groupItems as $item) { $bundleParts[] = $this->buildItemSku($item); } $skuParts[] = implode('|', $bundleParts); } // Build standalone items foreach ($standalone as $item) { $skuParts[] = $this->buildItemSku($item); } // Comma-separate all parts return implode(',', $skuParts); } /** * Build SKU for a single item with options. * * @param array{base_sku: string, options?: array} $item */ public function buildItemSku(array $item): string { $sku = strtoupper($item['base_sku']); foreach ($item['options'] ?? [] as $option) { $code = strtolower($option['code'] ?? $option[0] ?? ''); $value = $option['value'] ?? $option[1] ?? ''; $quantity = $option['quantity'] ?? $option[2] ?? 1; if ($code && $value) { $sku .= "-{$code}~{$value}"; if ($quantity > 1) { $sku .= "*{$quantity}"; } } } return $sku; } /** * Build from ParsedItem objects. * * @param array $items */ public function buildFromParsedItems(array $items, bool $asBundle = false): string { $skuParts = array_map( fn (ParsedItem $item) => $item->toString(), $items ); return implode($asBundle ? '|' : ',', $skuParts); } /** * Build from SkuParseResult (round-trip). */ public function buildFromResult(SkuParseResult $result): string { return $result->toString(); } /** * Generate bundle hash for discount creation. * * @param array $baseSkus Base SKUs without options */ public function generateBundleHash(array $baseSkus): string { $sorted = collect($baseSkus) ->map(fn (string $sku) => strtoupper($sku)) ->sort() ->implode('|'); return hash('sha256', $sorted); } /** * Add entity lineage prefix to a base SKU. * * @param string $baseSku The product SKU * @param array $entityCodes Entity codes in order [M1, M2, M3...] */ public function addLineage(string $baseSku, array $entityCodes): string { if (empty($entityCodes)) { return strtoupper($baseSku); } $prefix = implode('-', array_map('strtoupper', $entityCodes)); return $prefix.'-'.strtoupper($baseSku); } /** * Build a complete compound SKU with entity lineage. * * @param array $entityCodes Entity codes [M1, M2, ...] * @param array $lineItems */ public function buildWithLineage(array $entityCodes, array $lineItems): string { // Add lineage to each item's base SKU $prefixedItems = array_map(function (array $item) use ($entityCodes) { $item['base_sku'] = $this->addLineage($item['base_sku'], $entityCodes); return $item; }, $lineItems); return $this->build($prefixedItems); } /** * Create a new option. */ public function option(string $code, string $value, int $quantity = 1): SkuOption { return new SkuOption($code, $value, $quantity); } /** * Create a new parsed item. * * @param array $options */ public function item(string $baseSku, array $options = []): ParsedItem { return new ParsedItem($baseSku, $options); } }