diff --git a/app/Console/Commands/SyncStoreReceiptProductCost.php b/app/Console/Commands/SyncStoreReceiptProductCost.php index 3164612c..6e9ff39a 100644 --- a/app/Console/Commands/SyncStoreReceiptProductCost.php +++ b/app/Console/Commands/SyncStoreReceiptProductCost.php @@ -108,6 +108,9 @@ class SyncStoreReceiptProductCost extends Command /** 线上代发价格的钉钉代发来源。 */ private const DING_DAIFA_TABLE = 'ding_online_daifa_price_mirror'; + /** @var array,amount:float}>>|null */ + private ?array $goodsProfitProfileScopeIndex = null; + /** * 订单侧两个来源:淘系(企微客户订单)与聚水潭订单。 * 两边的仓库字段不同:淘系明细没有 send_warehouse,用订单上的 wms_co_id; @@ -2089,13 +2092,28 @@ class SyncStoreReceiptProductCost extends Command return; } + $nonLeafOrderIds = []; + foreach ($unique as $row) { + $parentOrderId = trim((string) ($row['parent_o_id'] ?? '')); + if ($parentOrderId !== '' && $parentOrderId !== '0') { + $nonLeafOrderIds[$parentOrderId] = true; + } + } + + // 分单订单可能把同一组合父行拆到多个末级子单,商品利润组件仍是整单口径。 + // 只有所有子单的组件完全一致且父行金额合计对平时,才合并成一组组件。 + $this->applyGoodsProfitSplitFamilies($unique, $stats, $profiles); + $groups = []; foreach ($unique as $key => $row) { $source = trim((string) ($row['source'] ?? '')); $orderId = trim((string) ($row['o_id'] ?? '')); $shopSkuId = trim((string) ($row['shop_sku_id'] ?? '')); $shopItemId = trim((string) ($row['shop_i_id'] ?? '')); - if (in_array('', [$source, $orderId, $shopSkuId, $shopItemId], true)) { + if ( + in_array('', [$source, $orderId, $shopSkuId, $shopItemId], true) + || isset($nonLeafOrderIds[$orderId]) + ) { continue; } $profileKey = self::goodsProfitSplitProfileKey($source, $orderId, $shopSkuId, $shopItemId); @@ -2137,22 +2155,30 @@ class SyncStoreReceiptProductCost extends Command } $componentCodes = array_fill_keys(array_keys($components), true); $parentRows = []; - $bundleParentRows = []; + $activeBundleParentRows = []; foreach ($groupKeys as $key) { + if (! isset($unique[$key])) { + continue; + } $sku = mb_strtoupper(trim((string) ($unique[$key]['sku'] ?? ''))); if ($sku === '' || isset($componentCodes[$sku])) { continue; } $parentRows[$sku][] = $key; - if (self::isBundleParentRow($unique[$key])) { - $bundleParentRows[$sku][] = $key; + $quantity = (float) ($unique[$key]['qty'] ?? 0); + $amount = (float) ($unique[$key]['amount'] ?? 0); + if ( + self::isBundleParentRow($unique[$key]) + && ($quantity > 0 || abs($amount) > 0.005) + ) { + $activeBundleParentRows[$sku][] = $key; } } - // 订单镜像可能同时残留上一版组合组件。存在唯一组合父行时,只按 - // 该父行与商品利润原表组件对账;对平后同组旧组件会一并剔除。 - if (count($bundleParentRows) === 1) { - $parentRows = $bundleParentRows; + // 订单镜像可能同时残留上一版零数量父行。存在唯一有效组合父行时, + // 只按该父行与商品利润原表组件对账;零数量旧父行随同组一起剔除。 + if (count($activeBundleParentRows) === 1) { + $parentRows = $activeBundleParentRows; } if (count($parentRows) !== 1) { @@ -2199,24 +2225,13 @@ class SyncStoreReceiptProductCost extends Command foreach ($groupKeys as $key) { unset($unique[$key]); } - foreach ($validComponents as $code => $component) { - $rowKey = (string) ($firstParent['row_key'] ?? $parentSku) - . ':goods-profit:' . sha1($parentSku . "\0" . $code); - $unique[$rowKey] = array_merge($firstParent, [ - 'sku' => $code, - 'sku_type' => 'normal', - 'base_price' => 0.0, - 'item_name' => $code, - 'qty' => round((float) $component['qty'], 4), - 'amount' => round((float) $component['amount'], 2), - 'warehouse' => $resolvedWarehouse, - 'parent_o_id' => '', - 'row_key' => $rowKey, - 'src_combine_sku_id' => '', - 'properties_value' => '', - 'bundle_component_source' => 'goods_profit_current_order', - ]); - } + $this->addGoodsProfitSplitComponents( + $unique, + $firstParent, + $parentSku, + $validComponents, + $resolvedWarehouse, + ); $stats['bundle_stats']['goods_profit_split_groups']++; $stats['bundle_stats']['goods_profit_split_parent_rows'] += count($parentKeys); @@ -2225,6 +2240,373 @@ class SyncStoreReceiptProductCost extends Command $stats['bundle_stats']['goods_profit_split_component_codes'][$code] = true; } } + + // 极少数订单的活动父行使用了新平台 SKU,而商品利润组件仍挂在旧平台 SKU 下。 + // 同一订单、同一平台款式下仅有一个组件集合金额与活动父行对平时,允许跨 SKU 替换。 + $this->applyGoodsProfitCrossSkuSplits($unique, $stats, $profiles); + } + + /** + * 合并分单末级父行后应用商品利润组件。 + * + * @param array> $unique + * @param array $stats + * @param array> $profiles + */ + private function applyGoodsProfitSplitFamilies(array &$unique, array &$stats, array $profiles): void + { + $families = []; + foreach ($unique as $key => $row) { + $parentOrderId = trim((string) ($row['parent_o_id'] ?? '')); + if ( + $parentOrderId === '' + || $parentOrderId === '0' + || ! self::isBundleParentRow($row) + ) { + continue; + } + + $quantity = (float) ($row['qty'] ?? 0); + $amount = (float) ($row['amount'] ?? 0); + if ($quantity <= 0 && abs($amount) <= 0.005) { + continue; + } + + $source = trim((string) ($row['source'] ?? '')); + $shopSkuId = trim((string) ($row['shop_sku_id'] ?? '')); + $shopItemId = trim((string) ($row['shop_i_id'] ?? '')); + $parentSku = mb_strtoupper(trim((string) ($row['sku'] ?? ''))); + $orderId = trim((string) ($row['o_id'] ?? '')); + if ( + in_array('', [$source, $orderId, $shopSkuId, $shopItemId, $parentSku], true) + ) { + continue; + } + + $familyKey = $source . "\0" . $parentOrderId . "\0" . $shopSkuId . "\0" . $shopItemId; + $families[$familyKey]['first_parent_key'] = $families[$familyKey]['first_parent_key'] ?? $key; + $families[$familyKey]['parent_keys'][] = $key; + $families[$familyKey]['parent_skus'][$parentSku] = true; + $families[$familyKey]['profile_keys'][ + self::goodsProfitSplitProfileKey($source, $orderId, $shopSkuId, $shopItemId) + ] = true; + } + + foreach ($families as $family) { + if (count($family['parent_keys']) < 2 || count($family['parent_skus']) !== 1) { + continue; + } + + $componentSets = []; + foreach (array_keys($family['profile_keys']) as $profileKey) { + $components = $profiles[$profileKey]['components'] ?? null; + if (! is_array($components) || $components === []) { + continue 2; + } + $componentSets[] = $components; + } + if ($componentSets === []) { + continue; + } + $components = reset($componentSets); + foreach ($componentSets as $componentSet) { + if ($componentSet !== $components) { + continue 2; + } + } + + $validComponents = self::validGoodsProfitComponents($components); + $componentAmount = (float) array_sum(array_column($validComponents, 'amount')); + $parentAmount = 0.0; + foreach ($family['parent_keys'] as $parentKey) { + if (isset($unique[$parentKey])) { + $parentAmount += (float) ($unique[$parentKey]['amount'] ?? 0); + } + } + if ( + $validComponents === [] + || $parentAmount <= 0 + || abs(round($componentAmount - $parentAmount, 2)) > 0.01 + ) { + continue; + } + + $firstParent = $unique[$family['first_parent_key']]; + $familyRootOrderId = trim((string) ($firstParent['parent_o_id'] ?? '')); + $resolvedWarehouse = ''; + foreach ($unique as $row) { + $rowRootOrderId = trim((string) ($row['parent_o_id'] ?? '')); + if ($rowRootOrderId === '' || $rowRootOrderId === '0') { + $rowRootOrderId = trim((string) ($row['o_id'] ?? '')); + } + if ( + trim((string) ($row['source'] ?? '')) !== trim((string) ($firstParent['source'] ?? '')) + || $rowRootOrderId !== $familyRootOrderId + || trim((string) ($row['shop_sku_id'] ?? '')) !== trim((string) ($firstParent['shop_sku_id'] ?? '')) + || trim((string) ($row['shop_i_id'] ?? '')) !== trim((string) ($firstParent['shop_i_id'] ?? '')) + ) { + continue; + } + if (trim((string) ($row['warehouse'] ?? '')) !== '') { + $resolvedWarehouse = trim((string) $row['warehouse']); + break; + } + } + + $componentCodes = array_fill_keys(array_keys($validComponents), true); + $parentRows = 0; + foreach ($unique as $key => $row) { + $rowRootOrderId = trim((string) ($row['parent_o_id'] ?? '')); + if ($rowRootOrderId === '' || $rowRootOrderId === '0') { + $rowRootOrderId = trim((string) ($row['o_id'] ?? '')); + } + if ( + trim((string) ($row['source'] ?? '')) !== trim((string) ($firstParent['source'] ?? '')) + || $rowRootOrderId !== $familyRootOrderId + || trim((string) ($row['shop_sku_id'] ?? '')) !== trim((string) ($firstParent['shop_sku_id'] ?? '')) + || trim((string) ($row['shop_i_id'] ?? '')) !== trim((string) ($firstParent['shop_i_id'] ?? '')) + ) { + continue; + } + + $sku = mb_strtoupper(trim((string) ($row['sku'] ?? ''))); + if (! self::isBundleParentRow($row) && ! isset($componentCodes[$sku])) { + continue; + } + if (self::isBundleParentRow($row)) { + $parentRows++; + } + unset($unique[$key]); + } + + $parentSku = (string) array_key_first($family['parent_skus']); + $this->addGoodsProfitSplitComponents( + $unique, + $firstParent, + $parentSku, + $validComponents, + $resolvedWarehouse, + ); + $stats['bundle_stats']['goods_profit_split_groups']++; + $stats['bundle_stats']['goods_profit_split_parent_rows'] += $parentRows; + $stats['bundle_stats']['goods_profit_split_component_rows'] += count($validComponents); + foreach (array_keys($validComponents) as $code) { + $stats['bundle_stats']['goods_profit_split_component_codes'][$code] = true; + } + } + } + + /** + * 活动父行平台 SKU 与商品利润组件平台 SKU 不一致时,只在组件集合唯一时替换。 + * + * @param array> $unique + * @param array $stats + * @param array> $profiles + */ + private function applyGoodsProfitCrossSkuSplits(array &$unique, array &$stats, array $profiles): void + { + $profilesByScope = $this->buildGoodsProfitProfileScopeIndex($profiles); + + $parentsByScope = []; + foreach ($unique as $key => $row) { + $source = trim((string) ($row['source'] ?? '')); + $orderId = trim((string) ($row['o_id'] ?? '')); + $shopSkuId = trim((string) ($row['shop_sku_id'] ?? '')); + $shopItemId = trim((string) ($row['shop_i_id'] ?? '')); + $sku = mb_strtoupper(trim((string) ($row['sku'] ?? ''))); + if ( + in_array('', [$source, $orderId, $shopSkuId, $shopItemId, $sku], true) + || ! self::isBundleParentRow($row) + || (float) ($row['qty'] ?? 0) <= 0 + || (float) ($row['amount'] ?? 0) <= 0 + || isset($profiles[self::goodsProfitSplitProfileKey($source, $orderId, $shopSkuId, $shopItemId)]) + ) { + continue; + } + $scopeKey = $source . "\0" . $orderId . "\0" . $shopItemId; + $parentsByScope[$scopeKey]['keys'][] = $key; + $parentsByScope[$scopeKey]['skus'][$sku] = true; + $parentsByScope[$scopeKey]['amount'] = ($parentsByScope[$scopeKey]['amount'] ?? 0.0) + + (float) ($row['amount'] ?? 0); + $parentsByScope[$scopeKey]['shop_sku_id'] = $shopSkuId; + } + + foreach ($parentsByScope as $scopeKey => $parentGroup) { + if (count($parentGroup['skus']) !== 1 || (float) ($parentGroup['amount'] ?? 0) <= 0) { + continue; + } + + $candidates = []; + foreach ($profilesByScope[$scopeKey] ?? [] as $candidate) { + if ( + $candidate['shop_sku_id'] !== $parentGroup['shop_sku_id'] + && abs(round((float) $candidate['amount'] - (float) $parentGroup['amount'], 2)) <= 0.01 + ) { + $candidates[] = $candidate; + } + } + if (count($candidates) !== 1) { + continue; + } + + $candidate = $candidates[0]; + $componentCodes = array_fill_keys(array_keys($candidate['components']), true); + [$source, $orderId, $shopItemId] = explode("\0", $scopeKey); + $linkedRows = false; + $resolvedWarehouse = ''; + foreach ($unique as $row) { + if ( + trim((string) ($row['source'] ?? '')) !== $source + || trim((string) ($row['o_id'] ?? '')) !== $orderId + || trim((string) ($row['shop_sku_id'] ?? '')) !== $candidate['shop_sku_id'] + || trim((string) ($row['shop_i_id'] ?? '')) !== $shopItemId + ) { + continue; + } + if (trim((string) ($row['warehouse'] ?? '')) !== '') { + $resolvedWarehouse = trim((string) $row['warehouse']); + } + $linkedParent = mb_strtoupper(trim((string) ($row['src_combine_sku_id'] ?? ''))); + if ( + self::isBundleParentRow($row) + || self::isChineseBundleParentSku($linkedParent) + ) { + $linkedRows = true; + } + } + if (! $linkedRows) { + continue; + } + + $firstParent = $unique[$parentGroup['keys'][0]]; + foreach ($unique as $key => $row) { + if ( + trim((string) ($row['source'] ?? '')) !== $source + || trim((string) ($row['o_id'] ?? '')) !== $orderId + || trim((string) ($row['shop_sku_id'] ?? '')) !== $candidate['shop_sku_id'] + || trim((string) ($row['shop_i_id'] ?? '')) !== $shopItemId + ) { + continue; + } + $sku = mb_strtoupper(trim((string) ($row['sku'] ?? ''))); + $linkedParent = mb_strtoupper(trim((string) ($row['src_combine_sku_id'] ?? ''))); + if ( + self::isBundleParentRow($row) + || isset($componentCodes[$sku]) + || self::isChineseBundleParentSku($linkedParent) + ) { + unset($unique[$key]); + } + } + foreach ($parentGroup['keys'] as $parentKey) { + unset($unique[$parentKey]); + } + + $parentSku = (string) array_key_first($parentGroup['skus']); + $this->addGoodsProfitSplitComponents( + $unique, + $firstParent, + $parentSku, + $candidate['components'], + $resolvedWarehouse, + ); + $stats['bundle_stats']['goods_profit_split_groups']++; + $stats['bundle_stats']['goods_profit_split_parent_rows'] += count($parentGroup['keys']); + $stats['bundle_stats']['goods_profit_split_component_rows'] += count($candidate['components']); + foreach (array_keys($candidate['components']) as $code) { + $stats['bundle_stats']['goods_profit_split_component_codes'][$code] = true; + } + } + } + + /** + * 商品利润画像全场一致,跨 SKU 候选索引只构建一次,避免每个账单重复扫描。 + * + * @param array> $profiles + * @return array,amount:float}>> + */ + private function buildGoodsProfitProfileScopeIndex(array $profiles): array + { + if ($this->goodsProfitProfileScopeIndex !== null) { + return $this->goodsProfitProfileScopeIndex; + } + + $index = []; + foreach ($profiles as $profileKey => $profile) { + $parts = explode("\0", $profileKey); + if (count($parts) !== 4 || ! is_array($profile['components'] ?? null)) { + continue; + } + $validComponents = self::validGoodsProfitComponents($profile['components']); + if ($validComponents === []) { + continue; + } + $scopeKey = $parts[0] . "\0" . $parts[1] . "\0" . $parts[3]; + $index[$scopeKey][] = [ + 'shop_sku_id' => $parts[2], + 'components' => $validComponents, + 'amount' => round((float) array_sum(array_column($validComponents, 'amount')), 2), + ]; + } + + return $this->goodsProfitProfileScopeIndex = $index; + } + + /** + * @param array $components + * @return array + */ + private static function validGoodsProfitComponents(array $components): array + { + $valid = []; + foreach ($components as $componentCode => $component) { + $code = mb_strtoupper(trim((string) $componentCode)); + $quantity = (float) ($component['qty'] ?? 0); + $amount = (float) ($component['amount'] ?? 0); + if ($code === '' || $quantity <= 0 || $amount <= 0) { + continue; + } + $valid[$code] = [ + 'qty' => round($quantity, 4), + 'amount' => round($amount, 2), + ]; + } + ksort($valid, SORT_STRING); + + return $valid; + } + + /** + * @param array> $unique + * @param array $firstParent + * @param array $components + */ + private function addGoodsProfitSplitComponents( + array &$unique, + array $firstParent, + string $parentSku, + array $components, + string $resolvedWarehouse, + ): void { + foreach ($components as $code => $component) { + $rowKey = (string) ($firstParent['row_key'] ?? $parentSku) + . ':goods-profit:' . sha1($parentSku . "\0" . $code); + $unique[$rowKey] = array_merge($firstParent, [ + 'sku' => $code, + 'sku_type' => 'normal', + 'base_price' => 0.0, + 'item_name' => $code, + 'qty' => round((float) $component['qty'], 4), + 'amount' => round((float) $component['amount'], 2), + 'warehouse' => $resolvedWarehouse, + 'parent_o_id' => '', + 'row_key' => $rowKey, + 'src_combine_sku_id' => '', + 'properties_value' => '', + 'bundle_component_source' => 'goods_profit_current_order', + 'keep_after_leaf_filter' => true, + ]); + } } /** @param array $row */ @@ -2774,7 +3156,8 @@ class SyncStoreReceiptProductCost extends Command if ($parentIds !== []) { $leaves = array_filter( $unique, - static fn (array $row): bool => ! isset($parentIds[trim((string) $row['o_id'])]) + static fn (array $row): bool => ! empty($row['keep_after_leaf_filter']) + || ! isset($parentIds[trim((string) $row['o_id'])]) ); // 兜底:理论上不会出现全部被过滤(除非数据自相矛盾),真有则保留原集合避免整单丢失。 if ($leaves !== []) { diff --git a/tests/Unit/StoreReceiptProductCostLineTest.php b/tests/Unit/StoreReceiptProductCostLineTest.php index 7ea0bb97..7350f4f1 100644 --- a/tests/Unit/StoreReceiptProductCostLineTest.php +++ b/tests/Unit/StoreReceiptProductCostLineTest.php @@ -641,6 +641,127 @@ class StoreReceiptProductCostLineTest extends TestCase $this->assertSame(649.0, round(array_sum(array_column($lines, 'amount')), 2)); } + public function test_goods_profit_bundle_parent_ignores_zero_quantity_stale_parent_rows(): void + { + $profileKey = "jst\0" . "8986587\0" . "3533103677164802\0" . 'GOODS-ITEM-ZERO-PARENTS'; + $components = [ + 'SJ038020N1410V02' => ['qty' => 1, 'amount' => 377.31], + 'SJ038069N1410V01' => ['qty' => 3, 'amount' => 42.69], + ]; + + [$lines, , , , , $stats] = $this->costLines([ + $this->row('old-parent-1', '运营组合套装12630', 'GOODS-ITEM-ZERO-PARENTS', 0, 0), + $this->row('old-parent-2', '运营组合套装13014', 'GOODS-ITEM-ZERO-PARENTS', 0, 0), + $this->row('current-parent', '运营组合套装13015', 'GOODS-ITEM-ZERO-PARENTS', 1, 420), + ], [], [ + $profileKey => [ + 'source' => 'goods_profit_current_order', + 'components' => $components, + ], + ]); + + $codes = array_column($lines, 'sku'); + sort($codes, SORT_STRING); + + $this->assertSame(['SJ038020N1410V02', 'SJ038069N1410V01'], $codes); + $this->assertSame(420.0, round(array_sum(array_column($lines, 'amount')), 2)); + $this->assertSame(1, $stats['bundle_stats']['goods_profit_split_groups']); + $this->assertSame(1, $stats['bundle_stats']['goods_profit_split_parent_rows']); + } + + public function test_goods_profit_bundle_combines_split_parent_branches_before_reconcile(): void + { + $components = [ + 'DNZ038526N1280V01' => ['qty' => 2, 'amount' => 1111.84], + 'GBGJ-DNZ038401N1440V01' => ['qty' => 2, 'amount' => 954.58], + 'GBGJ-ZWJ024903AN1407V01' => ['qty' => 2, 'amount' => 633.90], + 'GBGJ-ZWJ024903BN1407V01' => ['qty' => 2, 'amount' => 633.90], + ]; + $common = [ + 'shop_sku_id' => 'SPLIT-SKU', + ]; + + [$lines, , , , , $stats] = $this->costLines([ + $this->row('split-root', 'MKH-运营组合套装260706016', 'SPLIT-ITEM', 2, 3334.22, '', $common + [ + 'o_id' => 'SPLIT-ROOT', + ]), + $this->row('split-child-1', 'MKH-运营组合套装260706016', 'SPLIT-ITEM', 1, 1667.11, '', $common + [ + 'o_id' => 'SPLIT-CHILD-1', + 'parent_o_id' => 'SPLIT-ROOT', + ]), + $this->row('split-child-2', 'MKH-运营组合套装260706016', 'SPLIT-ITEM', 1, 1667.11, '', $common + [ + 'o_id' => 'SPLIT-CHILD-2', + 'parent_o_id' => 'SPLIT-ROOT', + ]), + ], [], [ + "jst\0SPLIT-CHILD-1\0SPLIT-SKU\0SPLIT-ITEM" => [ + 'source' => 'goods_profit_current_order', + 'components' => $components, + ], + "jst\0SPLIT-CHILD-2\0SPLIT-SKU\0SPLIT-ITEM" => [ + 'source' => 'goods_profit_current_order', + 'components' => $components, + ], + ]); + + $bySku = collect($lines)->keyBy('sku'); + $this->assertCount(4, $lines); + $this->assertSame(2.0, $bySku['DNZ038526N1280V01']['qty']); + $this->assertSame(3334.22, round(array_sum(array_column($lines, 'amount')), 2)); + $this->assertSame(1, $stats['bundle_stats']['goods_profit_split_groups']); + $this->assertSame(3, $stats['bundle_stats']['goods_profit_split_parent_rows']); + } + + public function test_goods_profit_bundle_uses_unique_component_group_from_stale_shop_sku(): void + { + $components = [ + 'SJ038020N1410V02' => ['qty' => 1, 'amount' => 377.31], + 'SJ038069N1410V01' => ['qty' => 3, 'amount' => 42.69], + ]; + $common = [ + 'o_id' => 'CROSS-SKU-ORDER', + 'shop_i_id' => 'CROSS-SKU-ITEM', + ]; + + [$lines, , , , , $stats] = $this->costLines([ + $this->row('old-parent', '运营组合套装12630', 'CROSS-SKU-ITEM', 0, 0, '', $common + [ + 'shop_sku_id' => 'OLD-SKU', + ]), + $this->row('component-1', 'SJ038020N1410V02', 'CROSS-SKU-ITEM', 1, 377.31, '运营组合套装12630', $common + [ + 'shop_sku_id' => 'OLD-SKU', + ]), + $this->row('component-2', 'SJ038069N1410V01', 'CROSS-SKU-ITEM', 3, 42.69, '运营组合套装12630', $common + [ + 'shop_sku_id' => 'OLD-SKU', + ]), + $this->row('current-parent', '运营组合套装13011', 'CROSS-SKU-ITEM', 1, 420, '', $common + [ + 'shop_sku_id' => 'NEW-SKU', + ]), + $this->row('separate-item', 'SN021702N230V01', 'CROSS-SKU-ITEM', 1, 49.97, '', [ + 'o_id' => 'CROSS-SKU-CHILD', + 'parent_o_id' => 'CROSS-SKU-ORDER', + 'shop_i_id' => 'CROSS-SKU-ITEM', + 'shop_sku_id' => 'OTHER-SKU', + ]), + ], [], [ + "jst\0CROSS-SKU-ORDER\0OLD-SKU\0CROSS-SKU-ITEM" => [ + 'source' => 'goods_profit_current_order', + 'components' => $components, + ], + ]); + + $codes = array_column($lines, 'sku'); + sort($codes, SORT_STRING); + + $this->assertSame([ + 'SJ038020N1410V02', + 'SJ038069N1410V01', + 'SN021702N230V01', + ], $codes); + $this->assertSame(469.97, round(array_sum(array_column($lines, 'amount')), 2)); + $this->assertSame(1, $stats['bundle_stats']['goods_profit_split_groups']); + $this->assertSame(1, $stats['bundle_stats']['goods_profit_split_parent_rows']); + } + public function test_goods_profit_current_order_split_is_not_applied_when_amount_differs(): void { $profileKey = "jst\0" . "8986587\0" . "3533103677164802\0" . 'GOODS-ITEM-3';