'bronze', 'BRONZE MAKER' => 'bronze', 'VIP II' => 'silver', 'SILVER MAKER' => 'silver', 'VIP III' => 'gold', 'GOLD MAKER' => 'gold', ]; protected $needColumnCheck = true; protected $validColumnNames = [self::COL_EMAIL, self::COL_TIER, self::COL_POINTS]; protected $_availableBehaviors = [ImportModel::BEHAVIOR_APPEND]; protected $masterAttributeCode = self::COL_EMAIL; private array $seenEmails = []; public function __construct( StringUtils $string, ScopeConfigInterface $scopeConfig, ImportFactory $importFactory, Helper $resourceHelper, ResourceConnection $resource, ProcessingErrorAggregatorInterface $errorAggregator, private readonly ResourceConnection $rewardResource, private readonly BalanceManager $balances, array $data = [], ?Json $serializer = null ) { parent::__construct($string, $scopeConfig, $importFactory, $resourceHelper, $resource, $errorAggregator, $data, $serializer); $this->addMessageTemplate(self::ERROR_EMAIL, 'Email format is invalid'); $this->addMessageTemplate(self::ERROR_TIER, 'Member tier is invalid; use VIP I/II/III or Bronze/Silver/Gold Maker'); $this->addMessageTemplate(self::ERROR_POINTS, 'Points must be a non-negative integer'); $this->addMessageTemplate(self::ERROR_NOT_FOUND, '未找到该邮箱对应的客户'); $this->addMessageTemplate(self::ERROR_MULTIPLE, '该邮箱存在于多个 Website,请人工确认归属'); $this->addMessageTemplate(self::ERROR_DUPLICATE, 'CSV 中邮箱重复'); } public function getEntityTypeCode(): string { return self::ENTITY_CODE; } public function validateRow(array $rowData, $rowNumber): bool { if (isset($this->_validatedRows[$rowNumber])) { return !$this->getErrorAggregator()->isRowInvalid($rowNumber); } $this->_validatedRows[$rowNumber] = true; $email = strtolower(trim((string)($rowData[self::COL_EMAIL] ?? ''))); $tier = $this->normalizeTier((string)($rowData[self::COL_TIER] ?? '')); $points = trim((string)($rowData[self::COL_POINTS] ?? '')); if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { $this->addRowError(self::ERROR_EMAIL, $rowNumber, self::COL_EMAIL); } if ($tier === null) { $this->addRowError(self::ERROR_TIER, $rowNumber, self::COL_TIER); } if (!preg_match('/^\d+$/', $points)) { $this->addRowError(self::ERROR_POINTS, $rowNumber, self::COL_POINTS); } if ($email !== '') { if (isset($this->seenEmails[$email])) { $this->addRowError(self::ERROR_DUPLICATE, $rowNumber, self::COL_EMAIL); } $this->seenEmails[$email] = true; $count = count($this->findCustomers($email)); if ($count === 0) { $this->addRowError(self::ERROR_NOT_FOUND, $rowNumber, self::COL_EMAIL); } if ($count > 1) { $this->addRowError(self::ERROR_MULTIPLE, $rowNumber, self::COL_EMAIL); } } return !$this->getErrorAggregator()->isRowInvalid($rowNumber); } protected function _importData() { $batchId = 'admin-import-' . gmdate('YmdHis') . '-' . bin2hex(random_bytes(4)); while ($bunch = $this->_dataSourceModel->getNextUniqueBunch($this->getIds())) { foreach ($bunch as $rowNumber => $rowData) { if (!$this->validateRow($rowData, $rowNumber)) { continue; } $email = strtolower(trim((string)$rowData[self::COL_EMAIL])); $customers = $this->findCustomers($email); $customer = reset($customers); $this->balances->importLegacySnapshot( (int)$customer['entity_id'], (int)$customer['website_id'], (int)$rowData[self::COL_POINTS], (string)$this->normalizeTier((string)$rowData[self::COL_TIER]), $batchId, ['source' => 'admin_import', 'source_row' => $rowNumber, 'source_email' => $email] ); $this->countItemsUpdated++; } } return true; } private function normalizeTier(string $value): ?string { $label = strtoupper((string)preg_replace('/\s+/', ' ', trim($value))); return self::TIER_MAP[$label] ?? null; } /** @return array */ private function findCustomers(string $email): array { $table = $this->rewardResource->getTableName('customer_entity'); return $this->rewardResource->getConnection()->fetchAll( $this->rewardResource->getConnection()->select() ->from($table, ['entity_id', 'website_id']) ->where('LOWER(email) = ?', strtolower(trim($email))) ); } }