|
@@ -0,0 +1,522 @@
|
|
|
|
|
+<?php
|
|
|
|
|
+
|
|
|
|
|
+namespace app\common\library;
|
|
|
|
|
+
|
|
|
|
|
+use think\Db;
|
|
|
|
|
+use think\Log;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 协助采购 — 供应商服务评分
|
|
|
|
|
+ *
|
|
|
|
|
+ * 总分 = 上年度质量评分×质量权重% + 单价评分值×单价权重%
|
|
|
|
|
+ * 单价合计 = 同一供应商本单各工序单价之和;单价评分值 = (本单最低合计 / 本供应商合计) × 100
|
|
|
|
|
+ * 权重取 supplier_score_rule 中 is_default=1 的规则;开标验证通过后写入 supplier_service_score
|
|
|
|
|
+ */
|
|
|
|
|
+class ProcuremenSupplierScore
|
|
|
|
|
+{
|
|
|
|
|
+ public const TABLE_RULE = 'supplier_score_rule';
|
|
|
|
|
+ public const TABLE_SCORE = 'supplier_service_score';
|
|
|
|
|
+
|
|
|
|
|
+ /** @var bool|null */
|
|
|
|
|
+ protected static $schemaReady = null;
|
|
|
|
|
+
|
|
|
|
|
+ public static function ensureSchema(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ if (self::$schemaReady === true) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ Db::query('SELECT 1 FROM `' . self::TABLE_RULE . '` LIMIT 1');
|
|
|
|
|
+ Db::query('SELECT 1 FROM `' . self::TABLE_SCORE . '` LIMIT 1');
|
|
|
|
|
+ self::$schemaReady = true;
|
|
|
|
|
+ self::ensureDefaultRule();
|
|
|
|
|
+ self::ensureScoreWeightColumns();
|
|
|
|
|
+
|
|
|
|
|
+ return;
|
|
|
|
|
+ } catch (\Throwable $e) {
|
|
|
|
|
+ }
|
|
|
|
|
+ $sqlFile = APP_PATH . 'extra' . DIRECTORY_SEPARATOR . 'supplier_score_install.sql';
|
|
|
|
|
+ if (is_file($sqlFile)) {
|
|
|
|
|
+ $sql = file_get_contents($sqlFile);
|
|
|
|
|
+ if (is_string($sql) && $sql !== '') {
|
|
|
|
|
+ foreach (preg_split('/;\s*[\r\n]+/', $sql) as $stmt) {
|
|
|
|
|
+ $stmt = trim($stmt);
|
|
|
|
|
+ if ($stmt === '' || stripos($stmt, 'CREATE TABLE') === false) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ Db::execute($stmt);
|
|
|
|
|
+ } catch (\Throwable $ignore) {
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ self::$schemaReady = true;
|
|
|
|
|
+ self::ensureDefaultRule();
|
|
|
|
|
+ self::ensureScoreWeightColumns();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 历史表补质量/价格百分比字段 */
|
|
|
|
|
+ protected static function ensureScoreWeightColumns(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ try {
|
|
|
|
|
+ $cols = Db::query("SHOW COLUMNS FROM `" . self::TABLE_SCORE . "` LIKE 'quality_weight'");
|
|
|
|
|
+ if (!is_array($cols) || $cols === []) {
|
|
|
|
|
+ Db::execute(
|
|
|
|
|
+ "ALTER TABLE `" . self::TABLE_SCORE . "` ADD COLUMN `quality_weight` decimal(5,2) NOT NULL DEFAULT 50.00 COMMENT '质量百分比' AFTER `quality_score`"
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ $cols2 = Db::query("SHOW COLUMNS FROM `" . self::TABLE_SCORE . "` LIKE 'price_weight'");
|
|
|
|
|
+ if (!is_array($cols2) || $cols2 === []) {
|
|
|
|
|
+ Db::execute(
|
|
|
|
|
+ "ALTER TABLE `" . self::TABLE_SCORE . "` ADD COLUMN `price_weight` decimal(5,2) NOT NULL DEFAULT 50.00 COMMENT '价格百分比' AFTER `price_sum`"
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (\Throwable $e) {
|
|
|
|
|
+ Log::write('supplier score weight columns: ' . $e->getMessage(), 'error');
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ protected static function ensureDefaultRule(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ try {
|
|
|
|
|
+ $cnt = (int)Db::table(self::TABLE_RULE)->count();
|
|
|
|
|
+ if ($cnt > 0) {
|
|
|
|
|
+ $hasDefault = Db::table(self::TABLE_RULE)->where('is_default', 1)->find();
|
|
|
|
|
+ if (!is_array($hasDefault)) {
|
|
|
|
|
+ $first = Db::table(self::TABLE_RULE)->order('id', 'asc')->find();
|
|
|
|
|
+ if (is_array($first)) {
|
|
|
|
|
+ Db::table(self::TABLE_RULE)->where('id', (int)$first['id'])->update([
|
|
|
|
|
+ 'is_default' => 1,
|
|
|
|
|
+ 'updatetime' => date('Y-m-d H:i:s'),
|
|
|
|
|
+ ]);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ $now = date('Y-m-d H:i:s');
|
|
|
|
|
+ Db::table(self::TABLE_RULE)->insert([
|
|
|
|
|
+ 'name' => '默认规则(质量50%+单价50%)',
|
|
|
|
|
+ 'quality_weight' => 50,
|
|
|
|
|
+ 'price_weight' => 50,
|
|
|
|
|
+ 'is_default' => 1,
|
|
|
|
|
+ 'status' => 'normal',
|
|
|
|
|
+ 'createtime' => $now,
|
|
|
|
|
+ 'updatetime' => $now,
|
|
|
|
|
+ ]);
|
|
|
|
|
+ } catch (\Throwable $e) {
|
|
|
|
|
+ Log::write('supplier score ensureDefaultRule: ' . $e->getMessage(), 'error');
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @return array{id:int,name:string,quality_weight:float,price_weight:float}
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function getDefaultRule(): array
|
|
|
|
|
+ {
|
|
|
|
|
+ self::ensureSchema();
|
|
|
|
|
+ try {
|
|
|
|
|
+ $row = Db::table(self::TABLE_RULE)
|
|
|
|
|
+ ->where('is_default', 1)
|
|
|
|
|
+ ->where('status', 'normal')
|
|
|
|
|
+ ->order('id', 'desc')
|
|
|
|
|
+ ->find();
|
|
|
|
|
+ if (!is_array($row)) {
|
|
|
|
|
+ $row = Db::table(self::TABLE_RULE)->where('status', 'normal')->order('id', 'asc')->find();
|
|
|
|
|
+ }
|
|
|
|
|
+ if (is_array($row)) {
|
|
|
|
|
+ return [
|
|
|
|
|
+ 'id' => (int)($row['id'] ?? 0),
|
|
|
|
|
+ 'name' => trim((string)($row['name'] ?? '')),
|
|
|
|
|
+ 'quality_weight' => (float)($row['quality_weight'] ?? 50),
|
|
|
|
|
+ 'price_weight' => (float)($row['price_weight'] ?? 50),
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (\Throwable $e) {
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return [
|
|
|
|
|
+ 'id' => 0,
|
|
|
|
|
+ 'name' => '内置默认',
|
|
|
|
|
+ 'quality_weight' => 50.0,
|
|
|
|
|
+ 'price_weight' => 50.0,
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 按公司名批量取供应商 id / 上年度质量评分
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param array<int, string> $companyNames
|
|
|
|
|
+ * @return array<string, array{id:int,score:float}>
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function loadCustomerScoreMap(array $companyNames): array
|
|
|
|
|
+ {
|
|
|
|
|
+ $map = [];
|
|
|
|
|
+ $names = [];
|
|
|
|
|
+ foreach ($companyNames as $n) {
|
|
|
|
|
+ $n = trim((string)$n);
|
|
|
|
|
+ if ($n !== '') {
|
|
|
|
|
+ $names[$n] = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if ($names === []) {
|
|
|
|
|
+ return $map;
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ $rows = Db::table('customer')
|
|
|
|
|
+ ->where('company_name', 'in', array_keys($names))
|
|
|
|
|
+ ->field('id,company_name,score')
|
|
|
|
|
+ ->select();
|
|
|
|
|
+ } catch (\Throwable $e) {
|
|
|
|
|
+ return $map;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!is_array($rows)) {
|
|
|
|
|
+ return $map;
|
|
|
|
|
+ }
|
|
|
|
|
+ foreach ($rows as $r) {
|
|
|
|
|
+ if (!is_array($r)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ $cn = trim((string)($r['company_name'] ?? ''));
|
|
|
|
|
+ if ($cn === '') {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ $raw = $r['score'] ?? null;
|
|
|
|
|
+ $score = ($raw === null || $raw === '') ? 0.0 : (float)$raw;
|
|
|
|
|
+ $map[$cn] = [
|
|
|
|
|
+ 'id' => (int)($r['id'] ?? 0),
|
|
|
|
|
+ 'score' => $score,
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return $map;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 从报价组计算本单各供应商得分(不写库)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param array<int, array<string, mixed>> $quoteGroups loadAuditSupplierQuoteGroups 结构(name/lines[].amount)
|
|
|
|
|
+ * @return array<string, array{
|
|
|
|
|
+ * company_name:string,customer_id:int,quality_score:float,price_sum:float,
|
|
|
|
|
+ * price_score:float,score:float,rank_no:int,score_text:string,rank_text:string
|
|
|
|
|
+ * }>
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function calculateForQuoteGroups(array $quoteGroups, ?array $rule = null): array
|
|
|
|
|
+ {
|
|
|
|
|
+ $rule = $rule ?: self::getDefaultRule();
|
|
|
|
|
+ $qw = (float)($rule['quality_weight'] ?? 50);
|
|
|
|
|
+ $pw = (float)($rule['price_weight'] ?? 50);
|
|
|
|
|
+ $wSum = $qw + $pw;
|
|
|
|
|
+ if ($wSum <= 0) {
|
|
|
|
|
+ $qw = 50;
|
|
|
|
|
+ $pw = 50;
|
|
|
|
|
+ $wSum = 100;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $names = [];
|
|
|
|
|
+ foreach ($quoteGroups as $g) {
|
|
|
|
|
+ if (!is_array($g)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ $cn = trim((string)($g['name'] ?? $g['company_name'] ?? ''));
|
|
|
|
|
+ if ($cn !== '') {
|
|
|
|
|
+ $names[] = $cn;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ $custMap = self::loadCustomerScoreMap($names);
|
|
|
|
|
+
|
|
|
|
|
+ $items = [];
|
|
|
|
|
+ $priceSums = [];
|
|
|
|
|
+ foreach ($quoteGroups as $g) {
|
|
|
|
|
+ if (!is_array($g)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ $cn = trim((string)($g['name'] ?? $g['company_name'] ?? ''));
|
|
|
|
|
+ if ($cn === '') {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ $priceSum = 0.0;
|
|
|
|
|
+ $hasPrice = false;
|
|
|
|
|
+ foreach (($g['lines'] ?? []) as $ln) {
|
|
|
|
|
+ if (!is_array($ln)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!empty($ln['amount_quote_pending'])) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ $am = trim((string)($ln['amount'] ?? ''));
|
|
|
|
|
+ if ($am === '' || $am === '0' || $am === '0.00') {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!is_numeric($am)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ $priceSum += (float)$am;
|
|
|
|
|
+ $hasPrice = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ $quality = (float)($custMap[$cn]['score'] ?? 0);
|
|
|
|
|
+ $items[$cn] = [
|
|
|
|
|
+ 'company_name' => $cn,
|
|
|
|
|
+ 'customer_id' => (int)($custMap[$cn]['id'] ?? 0),
|
|
|
|
|
+ 'quality_score' => $quality,
|
|
|
|
|
+ 'price_sum' => $hasPrice ? $priceSum : 0.0,
|
|
|
|
|
+ 'has_price' => $hasPrice,
|
|
|
|
|
+ 'price_score' => 0.0,
|
|
|
|
|
+ 'score' => 0.0,
|
|
|
|
|
+ 'rank_no' => 0,
|
|
|
|
|
+ 'score_text' => '',
|
|
|
|
|
+ 'rank_text' => '',
|
|
|
|
|
+ ];
|
|
|
|
|
+ if ($hasPrice && $priceSum > 0) {
|
|
|
|
|
+ $priceSums[$cn] = $priceSum;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $minSum = null;
|
|
|
|
|
+ foreach ($priceSums as $ps) {
|
|
|
|
|
+ if ($minSum === null || $ps < $minSum) {
|
|
|
|
|
+ $minSum = $ps;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ foreach ($items as $cn => &$it) {
|
|
|
|
|
+ if (!empty($it['has_price']) && $it['price_sum'] > 0 && $minSum !== null && $minSum > 0) {
|
|
|
|
|
+ $it['price_score'] = round(($minSum / $it['price_sum']) * 100, 2);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $it['price_score'] = 0.0;
|
|
|
|
|
+ }
|
|
|
|
|
+ $it['score'] = round(
|
|
|
|
|
+ $it['quality_score'] * ($qw / $wSum) + $it['price_score'] * ($pw / $wSum),
|
|
|
|
|
+ 2
|
|
|
|
|
+ );
|
|
|
|
|
+ $it['score_text'] = self::formatScore($it['score']);
|
|
|
|
|
+ }
|
|
|
|
|
+ unset($it);
|
|
|
|
|
+
|
|
|
|
|
+ $sorted = $items;
|
|
|
|
|
+ uasort($sorted, function ($a, $b) {
|
|
|
|
|
+ $sa = (float)($a['score'] ?? 0);
|
|
|
|
|
+ $sb = (float)($b['score'] ?? 0);
|
|
|
|
|
+ if ($sa !== $sb) {
|
|
|
|
|
+ return $sb <=> $sa;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return strcmp((string)($a['company_name'] ?? ''), (string)($b['company_name'] ?? ''));
|
|
|
|
|
+ });
|
|
|
|
|
+ $rank = 0;
|
|
|
|
|
+ foreach ($sorted as $cn => $row) {
|
|
|
|
|
+ $rank++;
|
|
|
|
|
+ $items[$cn]['rank_no'] = $rank;
|
|
|
|
|
+ $items[$cn]['rank_text'] = (string)$rank;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return $items;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static function formatScore($n): string
|
|
|
|
|
+ {
|
|
|
|
|
+ $s = rtrim(rtrim(sprintf('%.2F', (float)$n), '0'), '.');
|
|
|
|
|
+
|
|
|
|
|
+ return $s === '' ? '0' : $s;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 评分规则文案(用于确认页 / 详情展示)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param array{quality_weight?:float|int|string,price_weight?:float|int|string}|null $rule
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function formatRuleFormulaText(?array $rule = null): string
|
|
|
|
|
+ {
|
|
|
|
|
+ $rule = $rule ?: self::getDefaultRule();
|
|
|
|
|
+ $qw = self::formatWeightPercent($rule['quality_weight'] ?? 50);
|
|
|
|
|
+ $pw = self::formatWeightPercent($rule['price_weight'] ?? 50);
|
|
|
|
|
+
|
|
|
|
|
+ return '(总分)=(质量分×' . $qw . '%)+(价格分×' . $pw . '%)';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ protected static function formatWeightPercent($n): string
|
|
|
|
|
+ {
|
|
|
|
|
+ $s = rtrim(rtrim(sprintf('%.2F', (float)$n), '0'), '.');
|
|
|
|
|
+
|
|
|
|
|
+ return $s === '' ? '0' : $s;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 开标验证通过后:计算并写入供应商服务评分表
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param array<int, array<string, mixed>> $quoteGroups
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function saveForOrder(string $ccydh, array $quoteGroups, ?string $ym = null): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $ccydh = trim($ccydh);
|
|
|
|
|
+ if ($ccydh === '') {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ self::ensureSchema();
|
|
|
|
|
+ $rule = self::getDefaultRule();
|
|
|
|
|
+ $calc = self::calculateForQuoteGroups($quoteGroups, $rule);
|
|
|
|
|
+ if ($calc === []) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if ($ym === null || $ym === '') {
|
|
|
|
|
+ $ym = date('Y-m');
|
|
|
|
|
+ }
|
|
|
|
|
+ $now = date('Y-m-d H:i:s');
|
|
|
|
|
+ try {
|
|
|
|
|
+ Db::table(self::TABLE_SCORE)->where('ccydh', $ccydh)->delete();
|
|
|
|
|
+ $rows = [];
|
|
|
|
|
+ foreach ($calc as $it) {
|
|
|
|
|
+ $rows[] = [
|
|
|
|
|
+ 'customer_id' => (int)($it['customer_id'] ?? 0),
|
|
|
|
|
+ 'company_name' => (string)($it['company_name'] ?? ''),
|
|
|
|
|
+ 'ym' => $ym,
|
|
|
|
|
+ 'ccydh' => $ccydh,
|
|
|
|
|
+ 'score' => (float)($it['score'] ?? 0),
|
|
|
|
|
+ 'rank_no' => (int)($it['rank_no'] ?? 0),
|
|
|
|
|
+ 'quality_score' => (float)($it['quality_score'] ?? 0),
|
|
|
|
|
+ 'quality_weight' => (float)($rule['quality_weight'] ?? 50),
|
|
|
|
|
+ 'price_sum' => (float)($it['price_sum'] ?? 0),
|
|
|
|
|
+ 'price_weight' => (float)($rule['price_weight'] ?? 50),
|
|
|
|
|
+ 'price_score' => (float)($it['price_score'] ?? 0),
|
|
|
|
|
+ 'rule_id' => (int)($rule['id'] ?? 0),
|
|
|
|
|
+ 'createtime' => $now,
|
|
|
|
|
+ 'updatetime' => $now,
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+ if ($rows !== []) {
|
|
|
|
|
+ Db::table(self::TABLE_SCORE)->insertAll($rows);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (\Throwable $e) {
|
|
|
|
|
+ Log::write('supplier service score save: ' . $e->getMessage(), 'error');
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 读取某订单已落库评分;无则返回空
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return array<string, array<string, mixed>>
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function loadSavedByCcydh(string $ccydh): array
|
|
|
|
|
+ {
|
|
|
|
|
+ $ccydh = trim($ccydh);
|
|
|
|
|
+ if ($ccydh === '') {
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+ self::ensureSchema();
|
|
|
|
|
+ try {
|
|
|
|
|
+ $rows = Db::table(self::TABLE_SCORE)->where('ccydh', $ccydh)->select();
|
|
|
|
|
+ } catch (\Throwable $e) {
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!is_array($rows)) {
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+ $out = [];
|
|
|
|
|
+ foreach ($rows as $r) {
|
|
|
|
|
+ if (!is_array($r)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ $cn = trim((string)($r['company_name'] ?? ''));
|
|
|
|
|
+ if ($cn === '') {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ $score = (float)($r['score'] ?? 0);
|
|
|
|
|
+ $rank = (int)($r['rank_no'] ?? 0);
|
|
|
|
|
+ $out[$cn] = [
|
|
|
|
|
+ 'company_name' => $cn,
|
|
|
|
|
+ 'customer_id' => (int)($r['customer_id'] ?? 0),
|
|
|
|
|
+ 'quality_score' => (float)($r['quality_score'] ?? 0),
|
|
|
|
|
+ 'price_sum' => (float)($r['price_sum'] ?? 0),
|
|
|
|
|
+ 'price_score' => (float)($r['price_score'] ?? 0),
|
|
|
|
|
+ 'score' => $score,
|
|
|
|
|
+ 'rank_no' => $rank,
|
|
|
|
|
+ 'score_text' => self::formatScore($score),
|
|
|
|
|
+ 'rank_text' => $rank > 0 ? (string)$rank : '',
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return $out;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 把排名/评分挂到报价组上(开标后可见)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param array<int, array<string, mixed>> $quoteGroups
|
|
|
|
|
+ * @return array<int, array<string, mixed>>
|
|
|
|
|
+ */
|
|
|
|
|
+ public static function attachToQuoteGroups(array $quoteGroups, string $ccydh, bool $quoteVisible): array
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!$quoteVisible || $quoteGroups === []) {
|
|
|
|
|
+ foreach ($quoteGroups as &$g) {
|
|
|
|
|
+ if (!is_array($g)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ $g['service_rank'] = '';
|
|
|
|
|
+ $g['service_score'] = '';
|
|
|
|
|
+ $g['service_rank_text'] = '';
|
|
|
|
|
+ $g['service_score_text'] = '';
|
|
|
|
|
+ $g['show_service_score'] = 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ unset($g);
|
|
|
|
|
+
|
|
|
|
|
+ return $quoteGroups;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $saved = self::loadSavedByCcydh($ccydh);
|
|
|
|
|
+ if ($saved === []) {
|
|
|
|
|
+ $saved = self::calculateForQuoteGroups($quoteGroups);
|
|
|
|
|
+ // 历史已开标但尚未落库:补写一次
|
|
|
|
|
+ try {
|
|
|
|
|
+ self::saveForOrder($ccydh, $quoteGroups);
|
|
|
|
|
+ $saved = self::loadSavedByCcydh($ccydh) ?: $saved;
|
|
|
|
|
+ } catch (\Throwable $e) {
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ foreach ($quoteGroups as &$g) {
|
|
|
|
|
+ if (!is_array($g)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+ $cn = trim((string)($g['name'] ?? $g['company_name'] ?? ''));
|
|
|
|
|
+ $hit = $saved[$cn] ?? null;
|
|
|
|
|
+ if (is_array($hit)) {
|
|
|
|
|
+ $g['service_rank'] = (int)($hit['rank_no'] ?? 0);
|
|
|
|
|
+ $g['service_score'] = (float)($hit['score'] ?? 0);
|
|
|
|
|
+ $g['service_rank_text'] = (string)($hit['rank_text'] ?? '');
|
|
|
|
|
+ $g['service_score_text'] = (string)($hit['score_text'] ?? '');
|
|
|
|
|
+ $g['show_service_score'] = 1;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ $g['service_rank'] = '';
|
|
|
|
|
+ $g['service_score'] = '';
|
|
|
|
|
+ $g['service_rank_text'] = '';
|
|
|
|
|
+ $g['service_score_text'] = '';
|
|
|
|
|
+ $g['show_service_score'] = 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ unset($g);
|
|
|
|
|
+
|
|
|
|
|
+ // 开标后按排名升序(第1名在最前);无排名的放最后
|
|
|
|
|
+ usort($quoteGroups, function ($a, $b) {
|
|
|
|
|
+ $ra = is_array($a) ? (int)($a['service_rank'] ?? 0) : 0;
|
|
|
|
|
+ $rb = is_array($b) ? (int)($b['service_rank'] ?? 0) : 0;
|
|
|
|
|
+ $ha = $ra > 0;
|
|
|
|
|
+ $hb = $rb > 0;
|
|
|
|
|
+ if ($ha !== $hb) {
|
|
|
|
|
+ return $ha ? -1 : 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ if ($ha && $ra !== $rb) {
|
|
|
|
|
+ return $ra <=> $rb;
|
|
|
|
|
+ }
|
|
|
|
|
+ $sa = is_array($a) ? (float)($a['service_score'] ?? 0) : 0.0;
|
|
|
|
|
+ $sb = is_array($b) ? (float)($b['service_score'] ?? 0) : 0.0;
|
|
|
|
|
+ if ($sa !== $sb) {
|
|
|
|
|
+ return $sb <=> $sa;
|
|
|
|
|
+ }
|
|
|
|
|
+ $na = is_array($a) ? trim((string)($a['name'] ?? $a['company_name'] ?? '')) : '';
|
|
|
|
|
+ $nb = is_array($b) ? trim((string)($b['name'] ?? $b['company_name'] ?? '')) : '';
|
|
|
|
|
+
|
|
|
|
|
+ return strcmp($na, $nb);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return $quoteGroups;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|