*/ public function page(): array { $allRows = $this->decoratedRows(); $months = $this->monthOptions($allRows); $supplierNames = $this->supplierOptions($allRows); $purchaserNames = $this->purchaserOptions($allRows); $filters = $this->filters(request()->query(), $months, $purchaserNames); $negativeRows = $this->filterRows($allRows, $filters); $filteredSupplierNames = array_values(array_unique(array_filter(array_map( static fn (array $row): string => trim((string) ($row['supplier'] ?? '')), $negativeRows, )))); $filteredPurchaserNames = $this->purchaserOptions($negativeRows); $unassignedSupplierNames = array_values(array_unique(array_filter(array_map( static fn (array $row): string => trim((string) ($row['purchaser'] ?? '')) === '' ? trim((string) ($row['supplier'] ?? '')) : '', $negativeRows, )))); $latestResults = $this->dingTalkWorkflow->latestResults( $filteredSupplierNames, (int) (company_id() ?: 1), ); $total = count($negativeRows); $pages = max((int) ceil($total / self::PER_PAGE), 1); $page = min(max((int) request()->query('page', 1), 1), $pages); $pageRows = array_slice($negativeRows, ($page - 1) * self::PER_PAGE, self::PER_PAGE); $items = array_map( static function (array $row) use ($latestResults): object { $supplier = trim((string) ($row['supplier'] ?? '')); $result = $latestResults[$supplier] ?? null; $row['handling_result'] = $result ? trim((string) $result->result_text) : ''; $row['handling_result_meta'] = $result ? '采购 ' . trim((string) $result->purchaser_name) . ' · ' . mb_substr((string) $result->submitted_at, 0, 16) : ''; return (object) $row; }, $pageRows, ); $negativeAlertFields = []; $fields = collect($this->registry->fields( PayablesFinalRegistry::SECTION_SUPPLIER, self::DATASET, ))->map( static fn (array $definition, string $name): array => ['name' => $name] + $definition, )->values()->all(); foreach ($fields as $field) { $negativeAlertFields[] = $field; if (($field['name'] ?? '') === 'negative_reason') { $negativeAlertFields[] = ['name' => 'purchaser', 'label' => '采购', 'type' => 'text']; $negativeAlertFields[] = ['name' => 'handling_result', 'label' => '处理结果', 'type' => 'text']; } } return [ 'view' => 'business.pages.negative-alerts.supplier-negative-alert', 'negativeAlertFields' => $negativeAlertFields, 'negativeAlertRows' => new LengthAwarePaginator( $items, $total, self::PER_PAGE, $page, [ 'path' => request()->url(), 'query' => request()->except('page'), ], ), 'negativeAlertMonths' => $months, 'negativeAlertMonth' => $filters['month'], 'negativeAlertSupplier' => $filters['supplier'], 'negativeAlertSupplierNames' => $supplierNames, 'negativeAlertChecker' => $filters['checker'], 'negativeAlertCheckerNames' => $this->textOptions($allRows, 'checker'), 'negativeAlertSettlementMethod' => $filters['settlement_method'], 'negativeAlertSettlementMethods' => $this->textOptions($allRows, 'settlement_method'), 'negativeAlertPurchaser' => $filters['purchaser'], 'negativeAlertPurchaserNames' => $purchaserNames, 'negativeAlertSupplierCount' => count($filteredSupplierNames), 'negativeAlertFilteredPurchaserCount' => count($filteredPurchaserNames), 'negativeAlertUnassignedSupplierCount' => count($unassignedSupplierNames), 'negativeAlertTotal' => $total, 'negativeAlertRobot' => $this->dingTalkWorkflow->configuration(), 'negativeAlertSourceUrl' => route('business.pages.show', [ 'section' => 'supplier-payables-final', 'page' => 'supplier-payables-report', ]), ]; } /** @param array $filters * @return list> */ public function rowsForFilters(array $filters): array { $allRows = $this->decoratedRows(); return $this->filterRows($allRows, $this->filters( $filters, $this->monthOptions($allRows), $this->purchaserOptions($allRows), )); } /** @return list> */ private function decoratedRows(): array { $rows = $this->negativeRows(); $supplierNames = array_values(array_unique(array_filter(array_map( static fn (array $row): string => trim((string) ($row['supplier'] ?? '')), $rows, )))); $purchasersBySupplier = []; if ($supplierNames !== []) { try { foreach ($this->supplierManagement->suppliersByNames($supplierNames) as $supplier) { $name = trim((string) ($supplier->scmname ?? '')); $buyer = trim((string) ($supplier->buyer ?? '')); if ($name !== '' && $buyer !== '') { $purchasersBySupplier[$name][$buyer] = true; } } } catch (Throwable $exception) { report($exception); } } $purchasersBySupplier = array_map( static fn (array $buyers): string => implode('、', array_keys($buyers)), $purchasersBySupplier, ); foreach ($rows as &$row) { $supplier = trim((string) ($row['supplier'] ?? '')); $row['purchaser'] = $purchasersBySupplier[$supplier] ?? ''; } unset($row); return $rows; } /** @return list> */ private function negativeRows(): array { $rows = $this->store->listRows( PayablesFinalActions::DATASET_PREFIX . 'supplier:' . self::DATASET, (int) (company_id() ?: 1), ); $negativeRows = []; foreach ($rows as $row) { $balance = $this->number($row['supplier_balance'] ?? null) ?? 0.0; $shouldPay = $this->number($row['should_pay'] ?? null) ?? 0.0; $estimatedPrice = $this->number($row['estimated_price'] ?? null) ?? 0.0; $alertAmount = $balance - $shouldPay + $estimatedPrice; if ($alertAmount >= 0) { continue; } $row['supplier_balance'] = $balance; $row['_negative_alert_amount'] = round($alertAmount, 2); $negativeRows[] = $row; } usort($negativeRows, static function (array $left, array $right): int { return strcmp( (string) ($right['summary_month'] ?? ''), (string) ($left['summary_month'] ?? ''), ) ?: strnatcasecmp( (string) ($left['supplier'] ?? ''), (string) ($right['supplier'] ?? ''), ); }); return $negativeRows; } /** @param list> $rows * @return list */ private function monthOptions(array $rows): array { $months = []; foreach ($rows as $row) { $month = trim((string) ($row['summary_month'] ?? '')); if (preg_match('/^\d{4}-\d{2}$/', $month) === 1) { $months[$month] = true; } } $months = array_keys($months); rsort($months); return $months; } /** @param list> $rows * @return list */ private function supplierOptions(array $rows): array { $names = array_values(array_unique(array_filter(array_map( static fn (array $row): string => trim((string) ($row['supplier'] ?? '')), $rows, )))); sort($names, SORT_NATURAL | SORT_FLAG_CASE); return $names; } /** @param list> $rows * @return list */ private function purchaserOptions(array $rows): array { $names = array_values(array_unique(array_filter(array_map( static fn (array $row): string => trim((string) ($row['purchaser'] ?? '')), $rows, )))); sort($names, SORT_NATURAL | SORT_FLAG_CASE); return $names; } /** @param list> $rows * @return list */ private function textOptions(array $rows, string $field): array { $values = array_values(array_unique(array_filter(array_map( static fn (array $row): string => trim((string) ($row[$field] ?? '')), $rows, )))); sort($values, SORT_NATURAL | SORT_FLAG_CASE); return $values; } /** @param array $input * @param list $months * @param list $purchasers * @return array{month:string,supplier:string,checker:string,settlement_method:string,purchaser:string} */ private function filters(array $input, array $months, array $purchasers): array { $text = static fn (string $key): string => mb_substr( trim(is_scalar($input[$key] ?? '') ? (string) ($input[$key] ?? '') : ''), 0, 191, ); $month = $text('month'); $purchaser = $text('purchaser'); return [ 'month' => in_array($month, $months, true) ? $month : '', 'supplier' => $text('supplier'), 'checker' => $text('checker'), 'settlement_method' => $text('settlement_method'), 'purchaser' => in_array($purchaser, $purchasers, true) ? $purchaser : '', ]; } /** @param list> $rows * @param array{month:string,supplier:string,checker:string,settlement_method:string,purchaser:string} $filters * @return list> */ private function filterRows(array $rows, array $filters): array { return array_values(array_filter( $rows, static function (array $row) use ($filters): bool { if ($filters['month'] !== '' && (string) ($row['summary_month'] ?? '') !== $filters['month']) { return false; } if ($filters['supplier'] !== '') { $supplierNeedle = mb_strtolower($filters['supplier']); $supplierHaystack = mb_strtolower(implode("\n", [ (string) ($row['supplier'] ?? ''), (string) ($row['company'] ?? ''), ])); if (! str_contains($supplierHaystack, $supplierNeedle)) { return false; } } foreach (['checker', 'settlement_method'] as $field) { if ($filters[$field] === '') { continue; } if (! str_contains( mb_strtolower((string) ($row[$field] ?? '')), mb_strtolower($filters[$field]), )) { return false; } } return $filters['purchaser'] === '' || trim((string) ($row['purchaser'] ?? '')) === $filters['purchaser']; }, )); } private function number(mixed $value): ?float { if ($value === null || trim((string) $value) === '') { return null; } $normalized = str_replace([',', ',', ' '], '', trim((string) $value)); return is_numeric($normalized) ? (float) $normalized : null; } }