|
|
@@ -8,14 +8,23 @@ use think\Log;
|
|
|
/**
|
|
|
* 协助采购 — 供应商服务评分
|
|
|
*
|
|
|
- * 总分 = 上年度质量评分×质量权重% + 单价评分值×单价权重%
|
|
|
- * 单价合计 = 同一供应商本单各工序单价之和;单价评分值 = (本单最低合计 / 本供应商合计) × 100
|
|
|
- * 权重取 supplier_score_rule 中 is_default=1 的规则;开标验证通过后写入 supplier_service_score
|
|
|
+ * 表分工:
|
|
|
+ * - supplier_service_score:订单明细(开标后按 ccydh×供应商写入,含本单总分 score)
|
|
|
+ * - supplier_service_final_score:月度汇总(ym×供应商;score=当月订单平均总分;final_score=人工最终得分)
|
|
|
+ *
|
|
|
+ * 总分 = 各分项×对应权重%(权重填 0 则该项不参与)
|
|
|
+ * 商务/技术得分 =(综合评分 + 月度评分)/ 2;
|
|
|
+ * 单价项按本单工序单价合计参与加权;权重按百分比计入总分。
|
|
|
+ * 工期合计 = 同一供应商本单各工序预估工期之和;交货期评分值 = (本单最短合计 / 本供应商合计) × 100
|
|
|
+ * 权重取 supplier_score_rule 中 is_default=1 的规则;开标验证通过后写入订单明细表,并同步月度总分
|
|
|
*/
|
|
|
class ProcuremenSupplierScore
|
|
|
{
|
|
|
public const TABLE_RULE = 'supplier_score_rule';
|
|
|
+ /** 订单明细评分表 */
|
|
|
public const TABLE_SCORE = 'supplier_service_score';
|
|
|
+ /** 月度汇总表(总分 + 最终得分) */
|
|
|
+ public const TABLE_FINAL = 'supplier_service_final_score';
|
|
|
|
|
|
/** @var bool|null */
|
|
|
protected static $schemaReady = null;
|
|
|
@@ -31,7 +40,9 @@ class ProcuremenSupplierScore
|
|
|
self::$schemaReady = true;
|
|
|
self::ensureDefaultRule();
|
|
|
self::ensureScoreWeightColumns();
|
|
|
+ self::ensureLeadColumns();
|
|
|
self::ensureIntegerColumns();
|
|
|
+ self::ensureFinalScoreTable();
|
|
|
|
|
|
return;
|
|
|
} catch (\Throwable $e) {
|
|
|
@@ -55,7 +66,315 @@ class ProcuremenSupplierScore
|
|
|
self::$schemaReady = true;
|
|
|
self::ensureDefaultRule();
|
|
|
self::ensureScoreWeightColumns();
|
|
|
+ self::ensureLeadColumns();
|
|
|
self::ensureIntegerColumns();
|
|
|
+ self::ensureFinalScoreTable();
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 月度汇总表(总分 score + 人工最终得分 final_score) */
|
|
|
+ public static function ensureFinalScoreTable(): void
|
|
|
+ {
|
|
|
+ $ddl = "CREATE TABLE IF NOT EXISTS `" . self::TABLE_FINAL . "` (
|
|
|
+ `id` int unsigned NOT NULL AUTO_INCREMENT,
|
|
|
+ `ym` char(7) NOT NULL DEFAULT '' COMMENT '年月 YYYY-MM',
|
|
|
+ `company_name` varchar(128) NOT NULL DEFAULT '' COMMENT '供应商名称',
|
|
|
+ `score` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '月度总分(当月订单平均)',
|
|
|
+ `final_score` decimal(10,2) DEFAULT NULL COMMENT '最终得分(人工,空=未填)',
|
|
|
+ `createtime` datetime DEFAULT NULL,
|
|
|
+ `updatetime` datetime DEFAULT NULL,
|
|
|
+ PRIMARY KEY (`id`),
|
|
|
+ UNIQUE KEY `uk_ym_company` (`ym`,`company_name`),
|
|
|
+ KEY `idx_ym` (`ym`)
|
|
|
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='供应商月度评分汇总'";
|
|
|
+ try {
|
|
|
+ Db::execute($ddl);
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ Log::write('supplier monthly score table: ' . $e->getMessage(), 'error');
|
|
|
+ }
|
|
|
+ self::ensureMonthlyScoreColumns();
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 历史月度表补总分字段,并将 final_score 改为可空 */
|
|
|
+ protected static function ensureMonthlyScoreColumns(): void
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ $cols = Db::query("SHOW COLUMNS FROM `" . self::TABLE_FINAL . "` LIKE 'score'");
|
|
|
+ if (!is_array($cols) || $cols === []) {
|
|
|
+ Db::execute(
|
|
|
+ "ALTER TABLE `" . self::TABLE_FINAL . "` ADD COLUMN `score` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '月度总分(当月订单平均)' AFTER `company_name`"
|
|
|
+ );
|
|
|
+ }
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ Log::write('supplier monthly score column: ' . $e->getMessage(), 'error');
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ $info = Db::query("SHOW COLUMNS FROM `" . self::TABLE_FINAL . "` LIKE 'final_score'");
|
|
|
+ if (is_array($info) && $info !== []) {
|
|
|
+ $null = strtoupper((string)($info[0]['Null'] ?? ''));
|
|
|
+ if ($null !== 'YES') {
|
|
|
+ Db::execute(
|
|
|
+ "ALTER TABLE `" . self::TABLE_FINAL . "` MODIFY COLUMN `final_score` decimal(10,2) DEFAULT NULL COMMENT '最终得分(人工,空=未填)'"
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ Log::write('supplier monthly final_score null: ' . $e->getMessage(), 'error');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按订单明细重算并写入某月月度总分(保留已填最终得分)
|
|
|
+ *
|
|
|
+ * @param array<int, string>|null $onlyCompanies 仅同步这些供应商;null=当月全部
|
|
|
+ */
|
|
|
+ public static function syncMonthlyScoresForYm(string $ym, ?array $onlyCompanies = null): void
|
|
|
+ {
|
|
|
+ $ym = trim($ym);
|
|
|
+ if (!preg_match('/^\d{4}-\d{2}$/', $ym)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ self::ensureSchema();
|
|
|
+ self::ensureFinalScoreTable();
|
|
|
+ try {
|
|
|
+ $query = Db::table(self::TABLE_SCORE)->where('ym', $ym)->field('company_name,score');
|
|
|
+ if (is_array($onlyCompanies) && $onlyCompanies !== []) {
|
|
|
+ $names = [];
|
|
|
+ foreach ($onlyCompanies as $n) {
|
|
|
+ $n = trim((string)$n);
|
|
|
+ if ($n !== '') {
|
|
|
+ $names[$n] = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if ($names === []) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ $query->where('company_name', 'in', array_keys($names));
|
|
|
+ }
|
|
|
+ $rows = $query->select();
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ Log::write('supplier monthly sync read: ' . $e->getMessage(), 'error');
|
|
|
+
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (!is_array($rows)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ $agg = [];
|
|
|
+ foreach ($rows as $r) {
|
|
|
+ if (!is_array($r)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $cn = trim((string)($r['company_name'] ?? ''));
|
|
|
+ if ($cn === '') {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (!isset($agg[$cn])) {
|
|
|
+ $agg[$cn] = ['sum' => 0.0, 'cnt' => 0];
|
|
|
+ }
|
|
|
+ $agg[$cn]['sum'] += (float)($r['score'] ?? 0);
|
|
|
+ $agg[$cn]['cnt']++;
|
|
|
+ }
|
|
|
+ $now = date('Y-m-d H:i:s');
|
|
|
+ foreach ($agg as $cn => $a) {
|
|
|
+ $avg = $a['cnt'] > 0 ? round($a['sum'] / $a['cnt'], 2) : 0.0;
|
|
|
+ try {
|
|
|
+ $exists = Db::table(self::TABLE_FINAL)->where('ym', $ym)->where('company_name', $cn)->find();
|
|
|
+ if (is_array($exists) && $exists !== []) {
|
|
|
+ Db::table(self::TABLE_FINAL)->where('id', (int)($exists['id'] ?? 0))->update([
|
|
|
+ 'score' => $avg,
|
|
|
+ 'updatetime' => $now,
|
|
|
+ ]);
|
|
|
+ } else {
|
|
|
+ Db::table(self::TABLE_FINAL)->insert([
|
|
|
+ 'ym' => $ym,
|
|
|
+ 'company_name' => $cn,
|
|
|
+ 'score' => $avg,
|
|
|
+ 'final_score' => null,
|
|
|
+ 'createtime' => $now,
|
|
|
+ 'updatetime' => $now,
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ Log::write('supplier monthly sync write: ' . $e->getMessage(), 'error');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 读取某月各供应商最终得分(仅已人工填写的)
|
|
|
+ *
|
|
|
+ * @return array<string, float> company_name => final_score
|
|
|
+ */
|
|
|
+ public static function loadFinalScoreMapByYm(string $ym): array
|
|
|
+ {
|
|
|
+ $ym = trim($ym);
|
|
|
+ if (!preg_match('/^\d{4}-\d{2}$/', $ym)) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ self::ensureFinalScoreTable();
|
|
|
+ try {
|
|
|
+ $rows = Db::table(self::TABLE_FINAL)
|
|
|
+ ->where('ym', $ym)
|
|
|
+ ->whereNotNull('final_score')
|
|
|
+ ->field('company_name,final_score')
|
|
|
+ ->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;
|
|
|
+ }
|
|
|
+ $out[$cn] = round((float)($r['final_score'] ?? 0), 2);
|
|
|
+ }
|
|
|
+
|
|
|
+ return $out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 读取某月月度汇总(总分 + 最终得分)
|
|
|
+ *
|
|
|
+ * @return array<string, array{score:float,final_score:?float,final_saved:int}>
|
|
|
+ */
|
|
|
+ public static function loadMonthlyMapByYm(string $ym): array
|
|
|
+ {
|
|
|
+ $ym = trim($ym);
|
|
|
+ if (!preg_match('/^\d{4}-\d{2}$/', $ym)) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ self::ensureFinalScoreTable();
|
|
|
+ self::syncMonthlyScoresForYm($ym);
|
|
|
+ try {
|
|
|
+ $rows = Db::table(self::TABLE_FINAL)->where('ym', $ym)->field('company_name,score,final_score')->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;
|
|
|
+ }
|
|
|
+ $hasFinal = array_key_exists('final_score', $r) && $r['final_score'] !== null && $r['final_score'] !== '';
|
|
|
+ $out[$cn] = [
|
|
|
+ 'score' => round((float)($r['score'] ?? 0), 2),
|
|
|
+ 'final_score' => $hasFinal ? round((float)$r['final_score'], 2) : null,
|
|
|
+ 'final_saved' => $hasFinal ? 1 : 0,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ return $out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量保存某月最终得分(不覆盖月度总分 score)
|
|
|
+ *
|
|
|
+ * @param array<int, array{company_name?:string,final_score?:mixed}> $items
|
|
|
+ * @return array{done:int,saved:int,cleared:int,error:string}
|
|
|
+ */
|
|
|
+ public static function saveFinalScoresForYm(string $ym, array $items): array
|
|
|
+ {
|
|
|
+ $ym = trim($ym);
|
|
|
+ $result = ['done' => 0, 'saved' => 0, 'cleared' => 0, 'error' => ''];
|
|
|
+ if (!preg_match('/^\d{4}-\d{2}$/', $ym)) {
|
|
|
+ $result['error'] = '月份无效';
|
|
|
+
|
|
|
+ return $result;
|
|
|
+ }
|
|
|
+ if ($items === []) {
|
|
|
+ $result['error'] = '没有可保存的数据';
|
|
|
+
|
|
|
+ return $result;
|
|
|
+ }
|
|
|
+ self::ensureFinalScoreTable();
|
|
|
+ try {
|
|
|
+ Db::query('SELECT 1 FROM `' . self::TABLE_FINAL . '` LIMIT 1');
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ $result['error'] = '月度汇总表未创建,请联系管理员';
|
|
|
+
|
|
|
+ return $result;
|
|
|
+ }
|
|
|
+ $now = date('Y-m-d H:i:s');
|
|
|
+ foreach ($items as $it) {
|
|
|
+ if (!is_array($it)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $cn = trim((string)($it['company_name'] ?? ''));
|
|
|
+ if ($cn === '') {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $raw = $it['final_score'] ?? '';
|
|
|
+ if ($raw === null || (is_string($raw) && trim($raw) === '')) {
|
|
|
+ try {
|
|
|
+ $exists = Db::table(self::TABLE_FINAL)->where('ym', $ym)->where('company_name', $cn)->find();
|
|
|
+ if (is_array($exists) && $exists !== []) {
|
|
|
+ Db::table(self::TABLE_FINAL)->where('id', (int)($exists['id'] ?? 0))->update([
|
|
|
+ 'final_score' => null,
|
|
|
+ 'updatetime' => $now,
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ $result['cleared']++;
|
|
|
+ $result['done']++;
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ Log::write('supplier final score clear: ' . $e->getMessage(), 'error');
|
|
|
+ $result['error'] = $e->getMessage();
|
|
|
+ }
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (!is_numeric($raw)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $final = round((float)$raw, 2);
|
|
|
+ try {
|
|
|
+ $exists = Db::table(self::TABLE_FINAL)->where('ym', $ym)->where('company_name', $cn)->find();
|
|
|
+ if (is_array($exists) && $exists !== []) {
|
|
|
+ Db::table(self::TABLE_FINAL)->where('id', (int)($exists['id'] ?? 0))->update([
|
|
|
+ 'final_score' => $final,
|
|
|
+ 'updatetime' => $now,
|
|
|
+ ]);
|
|
|
+ } else {
|
|
|
+ // 无月度行时先按订单回填总分,再写入最终得分
|
|
|
+ self::syncMonthlyScoresForYm($ym, [$cn]);
|
|
|
+ $exists2 = Db::table(self::TABLE_FINAL)->where('ym', $ym)->where('company_name', $cn)->find();
|
|
|
+ if (is_array($exists2) && $exists2 !== []) {
|
|
|
+ Db::table(self::TABLE_FINAL)->where('id', (int)($exists2['id'] ?? 0))->update([
|
|
|
+ 'final_score' => $final,
|
|
|
+ 'updatetime' => $now,
|
|
|
+ ]);
|
|
|
+ } else {
|
|
|
+ Db::table(self::TABLE_FINAL)->insert([
|
|
|
+ 'ym' => $ym,
|
|
|
+ 'company_name' => $cn,
|
|
|
+ 'score' => 0,
|
|
|
+ 'final_score' => $final,
|
|
|
+ 'createtime' => $now,
|
|
|
+ 'updatetime' => $now,
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ $result['saved']++;
|
|
|
+ $result['done']++;
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ Log::write('supplier final score save: ' . $e->getMessage(), 'error');
|
|
|
+ $result['error'] = $e->getMessage();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return $result;
|
|
|
}
|
|
|
|
|
|
/** 历史表补质量/价格百分比字段 */
|
|
|
@@ -79,6 +398,36 @@ class ProcuremenSupplierScore
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /** 历史表补交货期权重/得分字段 */
|
|
|
+ protected static function ensureLeadColumns(): void
|
|
|
+ {
|
|
|
+ $adds = [
|
|
|
+ self::TABLE_RULE => [
|
|
|
+ 'lead_weight' => "ADD COLUMN `lead_weight` int unsigned NOT NULL DEFAULT 0 COMMENT '交货期得分权重(%)' AFTER `price_weight`",
|
|
|
+ 'lead_enabled' => "ADD COLUMN `lead_enabled` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '1=交货期纳入总分' AFTER `lead_weight`",
|
|
|
+ ],
|
|
|
+ self::TABLE_SCORE => [
|
|
|
+ 'lead_days_sum' => "ADD COLUMN `lead_days_sum` int NOT NULL DEFAULT 0 COMMENT '本单工序工期合计(天)' AFTER `price_score`",
|
|
|
+ 'lead_weight' => "ADD COLUMN `lead_weight` int unsigned NOT NULL DEFAULT 0 COMMENT '交货期百分比' AFTER `lead_days_sum`",
|
|
|
+ 'lead_enabled' => "ADD COLUMN `lead_enabled` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '1=当时纳入总分' AFTER `lead_weight`",
|
|
|
+ 'lead_score' => "ADD COLUMN `lead_score` decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT '交货期评分值' AFTER `lead_enabled`",
|
|
|
+ ],
|
|
|
+ ];
|
|
|
+ foreach ($adds as $table => $cols) {
|
|
|
+ foreach ($cols as $col => $ddl) {
|
|
|
+ try {
|
|
|
+ $info = Db::query("SHOW COLUMNS FROM `{$table}` LIKE '{$col}'");
|
|
|
+ if (is_array($info) && $info !== []) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ Db::execute("ALTER TABLE `{$table}` {$ddl}");
|
|
|
+ } catch (\Throwable $e) {
|
|
|
+ Log::write("supplier score lead column {$table}.{$col}: " . $e->getMessage(), 'error');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/** 将权重/质量分/单价合计等字段改为整数(去掉 .00) */
|
|
|
protected static function ensureIntegerColumns(): void
|
|
|
{
|
|
|
@@ -86,12 +435,15 @@ class ProcuremenSupplierScore
|
|
|
self::TABLE_RULE => [
|
|
|
'quality_weight' => "int unsigned NOT NULL DEFAULT 50 COMMENT '上年度质量评分权重(%)'",
|
|
|
'price_weight' => "int unsigned NOT NULL DEFAULT 50 COMMENT '单价评分权重(%)'",
|
|
|
+ 'lead_weight' => "int unsigned NOT NULL DEFAULT 0 COMMENT '交货期得分权重(%)'",
|
|
|
],
|
|
|
self::TABLE_SCORE => [
|
|
|
'quality_score' => "int NOT NULL DEFAULT 0 COMMENT '上年度质量评分'",
|
|
|
'quality_weight' => "int unsigned NOT NULL DEFAULT 50 COMMENT '质量百分比'",
|
|
|
'price_sum' => "int NOT NULL DEFAULT 0 COMMENT '本单工序单价合计'",
|
|
|
'price_weight' => "int unsigned NOT NULL DEFAULT 50 COMMENT '价格百分比'",
|
|
|
+ 'lead_days_sum' => "int NOT NULL DEFAULT 0 COMMENT '本单工序工期合计(天)'",
|
|
|
+ 'lead_weight' => "int unsigned NOT NULL DEFAULT 0 COMMENT '交货期百分比'",
|
|
|
],
|
|
|
];
|
|
|
foreach ($defs as $table => $cols) {
|
|
|
@@ -133,7 +485,7 @@ class ProcuremenSupplierScore
|
|
|
}
|
|
|
$now = date('Y-m-d H:i:s');
|
|
|
Db::table(self::TABLE_RULE)->insert([
|
|
|
- 'name' => '默认规则(质量50%+单价50%)',
|
|
|
+ 'name' => '默认规则(商务/技术50%+单价50%)',
|
|
|
'quality_weight' => 50,
|
|
|
'price_weight' => 50,
|
|
|
'is_default' => 1,
|
|
|
@@ -147,7 +499,7 @@ class ProcuremenSupplierScore
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * @return array{id:int,name:string,quality_weight:int,price_weight:int}
|
|
|
+ * @return array{id:int,name:string,quality_weight:int,price_weight:int,lead_weight:int,lead_enabled:int}
|
|
|
*/
|
|
|
public static function getDefaultRule(): array
|
|
|
{
|
|
|
@@ -162,11 +514,16 @@ class ProcuremenSupplierScore
|
|
|
$row = Db::table(self::TABLE_RULE)->where('status', 'normal')->order('id', 'asc')->find();
|
|
|
}
|
|
|
if (is_array($row)) {
|
|
|
+ $lw = (int)($row['lead_weight'] ?? 0);
|
|
|
+
|
|
|
return [
|
|
|
'id' => (int)($row['id'] ?? 0),
|
|
|
'name' => trim((string)($row['name'] ?? '')),
|
|
|
'quality_weight' => (int)($row['quality_weight'] ?? 50),
|
|
|
'price_weight' => (int)($row['price_weight'] ?? 50),
|
|
|
+ 'lead_weight' => $lw,
|
|
|
+ // 权重大于 0 即纳入;兼容旧 lead_enabled 字段
|
|
|
+ 'lead_enabled' => $lw > 0 ? 1 : 0,
|
|
|
];
|
|
|
}
|
|
|
} catch (\Throwable $e) {
|
|
|
@@ -177,14 +534,16 @@ class ProcuremenSupplierScore
|
|
|
'name' => '内置默认',
|
|
|
'quality_weight' => 50,
|
|
|
'price_weight' => 50,
|
|
|
+ 'lead_weight' => 0,
|
|
|
+ 'lead_enabled' => 0,
|
|
|
];
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 按公司名批量取供应商 id / 上年度质量评分
|
|
|
+ * 按公司名批量取供应商 id / 综合评分 / 月度评分
|
|
|
*
|
|
|
* @param array<int, string> $companyNames
|
|
|
- * @return array<string, array{id:int,score:float}>
|
|
|
+ * @return array<string, array{id:int,score:float,monthly_score:?float,quality_avg:float}>
|
|
|
*/
|
|
|
public static function loadCustomerScoreMap(array $companyNames): array
|
|
|
{
|
|
|
@@ -202,10 +561,18 @@ class ProcuremenSupplierScore
|
|
|
try {
|
|
|
$rows = Db::table('customer')
|
|
|
->where('company_name', 'in', array_keys($names))
|
|
|
- ->field('id,company_name,score')
|
|
|
+ ->field('id,company_name,score,monthly_score')
|
|
|
->select();
|
|
|
} catch (\Throwable $e) {
|
|
|
- return $map;
|
|
|
+ // 无 monthly_score 列时回退只取综合评分
|
|
|
+ try {
|
|
|
+ $rows = Db::table('customer')
|
|
|
+ ->where('company_name', 'in', array_keys($names))
|
|
|
+ ->field('id,company_name,score')
|
|
|
+ ->select();
|
|
|
+ } catch (\Throwable $e2) {
|
|
|
+ return $map;
|
|
|
+ }
|
|
|
}
|
|
|
if (!is_array($rows)) {
|
|
|
return $map;
|
|
|
@@ -218,11 +585,27 @@ class ProcuremenSupplierScore
|
|
|
if ($cn === '') {
|
|
|
continue;
|
|
|
}
|
|
|
- $raw = $r['score'] ?? null;
|
|
|
- $score = ($raw === null || $raw === '') ? 0 : (int)round((float)$raw);
|
|
|
+ $compRaw = $r['score'] ?? null;
|
|
|
+ $monthRaw = $r['monthly_score'] ?? null;
|
|
|
+ $hasComp = !($compRaw === null || $compRaw === '');
|
|
|
+ $hasMonth = !($monthRaw === null || $monthRaw === '');
|
|
|
+ $comp = $hasComp ? (float)$compRaw : null;
|
|
|
+ $month = $hasMonth ? (float)$monthRaw : null;
|
|
|
+ if ($hasComp && $hasMonth) {
|
|
|
+ $avg = ((float)$comp + (float)$month) / 2;
|
|
|
+ } elseif ($hasComp) {
|
|
|
+ $avg = (float)$comp;
|
|
|
+ } elseif ($hasMonth) {
|
|
|
+ $avg = (float)$month;
|
|
|
+ } else {
|
|
|
+ $avg = 0.0;
|
|
|
+ }
|
|
|
$map[$cn] = [
|
|
|
- 'id' => (int)($r['id'] ?? 0),
|
|
|
- 'score' => $score,
|
|
|
+ 'id' => (int)($r['id'] ?? 0),
|
|
|
+ 'score' => $hasComp ? (float)$comp : 0.0,
|
|
|
+ 'monthly_score' => $hasMonth ? (float)$month : null,
|
|
|
+ // 商务/技术得分:综合分与月度分平均值
|
|
|
+ 'quality_avg' => round($avg, 2),
|
|
|
];
|
|
|
}
|
|
|
|
|
|
@@ -241,12 +624,17 @@ class ProcuremenSupplierScore
|
|
|
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;
|
|
|
+ $qw = max(0, (float)($rule['quality_weight'] ?? 50));
|
|
|
+ $pw = max(0, (float)($rule['price_weight'] ?? 50));
|
|
|
+ $lw = max(0, (float)($rule['lead_weight'] ?? 0));
|
|
|
+ // 填 0 的权重不参与计算
|
|
|
+ $leadEnabled = $lw > 0 ? 1 : 0;
|
|
|
+ $wSum = $qw + $pw + $lw;
|
|
|
if ($wSum <= 0) {
|
|
|
$qw = 50;
|
|
|
$pw = 50;
|
|
|
+ $lw = 0;
|
|
|
+ $leadEnabled = 0;
|
|
|
$wSum = 100;
|
|
|
}
|
|
|
|
|
|
@@ -264,6 +652,7 @@ class ProcuremenSupplierScore
|
|
|
|
|
|
$items = [];
|
|
|
$priceSums = [];
|
|
|
+ $leadSums = [];
|
|
|
foreach ($quoteGroups as $g) {
|
|
|
if (!is_array($g)) {
|
|
|
continue;
|
|
|
@@ -274,39 +663,68 @@ class ProcuremenSupplierScore
|
|
|
}
|
|
|
$priceSum = 0.0;
|
|
|
$hasPrice = false;
|
|
|
- foreach (($g['lines'] ?? []) as $ln) {
|
|
|
+ $leadSum = 0;
|
|
|
+ $hasLead = false;
|
|
|
+ // 确认页用 lines,审批页用 pick_lines
|
|
|
+ $lineRows = $g['lines'] ?? $g['pick_lines'] ?? [];
|
|
|
+ if (!is_array($lineRows)) {
|
|
|
+ $lineRows = [];
|
|
|
+ }
|
|
|
+ foreach ($lineRows 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 (empty($ln['amount_quote_pending'])) {
|
|
|
+ $am = self::resolveQuoteLineAmountRaw($ln);
|
|
|
+ if ($am !== '' && $am !== '0' && $am !== '0.00' && is_numeric($am)) {
|
|
|
+ $priceSum += (float)$am;
|
|
|
+ $hasPrice = true;
|
|
|
+ }
|
|
|
}
|
|
|
- if (!is_numeric($am)) {
|
|
|
- continue;
|
|
|
+ if (empty($ln['lead_days_quote_pending'])) {
|
|
|
+ $ldRaw = $ln['lead_days'] ?? null;
|
|
|
+ if ($ldRaw !== null && $ldRaw !== '' && is_numeric($ldRaw)) {
|
|
|
+ $ld = (int)$ldRaw;
|
|
|
+ if ($ld > 0) {
|
|
|
+ $leadSum += $ld;
|
|
|
+ $hasLead = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
- $priceSum += (float)$am;
|
|
|
- $hasPrice = true;
|
|
|
}
|
|
|
- $quality = (int)round((float)($custMap[$cn]['score'] ?? 0));
|
|
|
+ $quality = (float)($custMap[$cn]['quality_avg'] ?? 0);
|
|
|
+ $psum = $hasPrice ? round($priceSum, 2) : 0;
|
|
|
+ $lsum = $hasLead ? (int)$leadSum : 0;
|
|
|
$items[$cn] = [
|
|
|
- 'company_name' => $cn,
|
|
|
- 'customer_id' => (int)($custMap[$cn]['id'] ?? 0),
|
|
|
- 'quality_score' => $quality,
|
|
|
- 'price_sum' => $hasPrice ? (int)round($priceSum) : 0,
|
|
|
- 'has_price' => $hasPrice,
|
|
|
- 'price_score' => 0.0,
|
|
|
- 'score' => 0.0,
|
|
|
- 'rank_no' => 0,
|
|
|
- 'score_text' => '',
|
|
|
- 'rank_text' => '',
|
|
|
+ 'company_name' => $cn,
|
|
|
+ 'customer_id' => (int)($custMap[$cn]['id'] ?? 0),
|
|
|
+ 'quality_score' => $quality,
|
|
|
+ 'quality_score_text' => self::formatScore($quality),
|
|
|
+ 'quality_weight' => (int)$qw,
|
|
|
+ 'price_sum' => $psum,
|
|
|
+ 'price_sum_text' => self::formatScore($psum),
|
|
|
+ 'price_weight' => (int)$pw,
|
|
|
+ 'has_price' => $hasPrice,
|
|
|
+ 'price_score' => 0.0,
|
|
|
+ 'price_score_text' => '0',
|
|
|
+ 'lead_days_sum' => $lsum,
|
|
|
+ 'lead_days_sum_text' => (string)$lsum,
|
|
|
+ 'lead_weight' => (int)$lw,
|
|
|
+ 'lead_enabled' => $leadEnabled,
|
|
|
+ 'has_lead' => $hasLead,
|
|
|
+ 'lead_score' => 0.0,
|
|
|
+ 'lead_score_text' => '0',
|
|
|
+ 'score' => 0.0,
|
|
|
+ 'rank_no' => 0,
|
|
|
+ 'score_text' => '',
|
|
|
+ 'rank_text' => '',
|
|
|
];
|
|
|
if ($hasPrice && $priceSum > 0) {
|
|
|
$priceSums[$cn] = $priceSum;
|
|
|
}
|
|
|
+ if ($hasLead && $leadSum > 0) {
|
|
|
+ $leadSums[$cn] = $leadSum;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
$minSum = null;
|
|
|
@@ -315,16 +733,40 @@ class ProcuremenSupplierScore
|
|
|
$minSum = $ps;
|
|
|
}
|
|
|
}
|
|
|
+ $minLead = null;
|
|
|
+ foreach ($leadSums as $ls) {
|
|
|
+ if ($minLead === null || $ls < $minLead) {
|
|
|
+ $minLead = $ls;
|
|
|
+ }
|
|
|
+ }
|
|
|
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['price_score_text'] = self::formatScore($it['price_score']);
|
|
|
+
|
|
|
+ if ($leadEnabled && !empty($it['has_lead']) && $it['lead_days_sum'] > 0 && $minLead !== null && $minLead > 0) {
|
|
|
+ $it['lead_score'] = round(($minLead / $it['lead_days_sum']) * 100, 2);
|
|
|
+ } else {
|
|
|
+ $it['lead_score'] = 0.0;
|
|
|
+ }
|
|
|
+ $it['lead_score_text'] = self::formatScore($it['lead_score']);
|
|
|
+
|
|
|
+ // 总分 = 商务/技术得分×质量% + 工序单价合计×价格% [+ 交货期分×交货%]
|
|
|
+ $total = 0.0;
|
|
|
+ if ($qw > 0) {
|
|
|
+ $total += $it['quality_score'] * ($qw / 100);
|
|
|
+ }
|
|
|
+ if ($pw > 0) {
|
|
|
+ $total += (float)$it['price_sum'] * ($pw / 100);
|
|
|
+ }
|
|
|
+ if ($lw > 0) {
|
|
|
+ $total += $it['lead_score'] * ($lw / 100);
|
|
|
+ }
|
|
|
+ $it['score'] = round($total, 2);
|
|
|
$it['score_text'] = self::formatScore($it['score']);
|
|
|
}
|
|
|
unset($it);
|
|
|
@@ -356,18 +798,125 @@ class ProcuremenSupplierScore
|
|
|
return $s === '' ? '0' : $s;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 报价行单价原文(兼容确认页 amount、审批页 unit_price_text/amount_text)
|
|
|
+ *
|
|
|
+ * @param array<string, mixed> $ln
|
|
|
+ */
|
|
|
+ protected static function resolveQuoteLineAmountRaw(array $ln): string
|
|
|
+ {
|
|
|
+ foreach (['amount', 'unit_price_text', 'amount_text'] as $k) {
|
|
|
+ if (!array_key_exists($k, $ln) || $ln[$k] === null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ $am = trim((string)$ln[$k]);
|
|
|
+ if ($am === '' || $am === '未填写' || $am === '未开标验证' || $am === '开标验证后查看') {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ // 去掉千分位等
|
|
|
+ $am = str_replace([',', ',', ' '], '', $am);
|
|
|
+ if ($am !== '' && is_numeric($am)) {
|
|
|
+ return $am;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 总分留痕文案:59 = (100×50%)+(18×50%)
|
|
|
+ * 权重大于 0 的项才写入;数值取商务/技术得分、工序单价合计(及交货期分)
|
|
|
+ *
|
|
|
+ * @param array<string, mixed> $hit calculate/loadSaved 单项
|
|
|
+ */
|
|
|
+ public static function formatScoreDetailText(array $hit): string
|
|
|
+ {
|
|
|
+ $scoreText = self::formatScore($hit['score'] ?? 0);
|
|
|
+ $qw = (float)($hit['quality_weight'] ?? 0);
|
|
|
+ $pw = (float)($hit['price_weight'] ?? 0);
|
|
|
+ $lw = (float)($hit['lead_weight'] ?? 0);
|
|
|
+ $parts = [];
|
|
|
+ if ($qw > 0) {
|
|
|
+ $parts[] = '(' . self::formatScore($hit['quality_score'] ?? 0) . '×' . self::formatWeightPercent($qw) . '%)';
|
|
|
+ }
|
|
|
+ if ($pw > 0) {
|
|
|
+ $parts[] = '(' . self::formatScore($hit['price_sum'] ?? 0) . '×' . self::formatWeightPercent($pw) . '%)';
|
|
|
+ }
|
|
|
+ if ($lw > 0) {
|
|
|
+ $parts[] = '(' . self::formatScore($hit['lead_score'] ?? 0) . '×' . self::formatWeightPercent($lw) . '%)';
|
|
|
+ }
|
|
|
+ if ($parts === []) {
|
|
|
+ return $scoreText;
|
|
|
+ }
|
|
|
+
|
|
|
+ return $scoreText . ' = ' . implode('+', $parts);
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 评分规则文案(用于确认页 / 详情展示)
|
|
|
+ * 百分比取当前默认规则;权重大于 0 的项才写入公式
|
|
|
*
|
|
|
- * @param array{quality_weight?:float|int|string,price_weight?:float|int|string}|null $rule
|
|
|
+ * @param array{quality_weight?:float|int|string,price_weight?:float|int|string,lead_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);
|
|
|
+ $qw = (float)($rule['quality_weight'] ?? 50);
|
|
|
+ $pw = (float)($rule['price_weight'] ?? 50);
|
|
|
+ $lw = (float)($rule['lead_weight'] ?? 0);
|
|
|
+ $parts = [];
|
|
|
+ if ($qw > 0) {
|
|
|
+ $parts[] = '(商务/技术得分×' . self::formatWeightPercent($qw) . '%)';
|
|
|
+ }
|
|
|
+ if ($pw > 0) {
|
|
|
+ $parts[] = '(工序单价合计×' . self::formatWeightPercent($pw) . '%)';
|
|
|
+ }
|
|
|
+ if ($lw > 0) {
|
|
|
+ $parts[] = '(交货得分×' . self::formatWeightPercent($lw) . '%)';
|
|
|
+ }
|
|
|
+ if ($parts === []) {
|
|
|
+ return '(总分)=(商务/技术得分×50%)+(工序单价合计×50%)';
|
|
|
+ }
|
|
|
|
|
|
- return '(总分)=(质量分×' . $qw . '%)+(价格分×' . $pw . '%)';
|
|
|
+ return '(总分)=' . implode('+', $parts);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 报价组是否展示交货期留痕列(已落库纳入,或尚无落库且当前默认规则交货分>0)
|
|
|
+ *
|
|
|
+ * @param array<int, array<string, mixed>> $quoteGroups
|
|
|
+ */
|
|
|
+ public static function detectShowLeadScore(array $quoteGroups = [], ?array $rule = null): bool
|
|
|
+ {
|
|
|
+ $hasSavedScore = false;
|
|
|
+ foreach ($quoteGroups as $g) {
|
|
|
+ if (!is_array($g)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (!empty($g['show_service_lead_score'])) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ if (!empty($g['show_service_score'])) {
|
|
|
+ $hasSavedScore = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 历史单已按旧规则落库且交货期权重为 0:不显示这两列
|
|
|
+ if ($hasSavedScore) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ $rule = $rule ?: self::getDefaultRule();
|
|
|
+
|
|
|
+ return (float)($rule['lead_weight'] ?? 0) > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 评分规则提示:始终按当前默认规则比例显示(切换默认后即时变化)
|
|
|
+ *
|
|
|
+ * @param array<int, array<string, mixed>> $quoteGroups 保留参数兼容调用方,不参与文案
|
|
|
+ */
|
|
|
+ public static function formatRuleFormulaTextForGroups(array $quoteGroups = []): string
|
|
|
+ {
|
|
|
+ return self::formatRuleFormulaText();
|
|
|
}
|
|
|
|
|
|
protected static function formatWeightPercent($n): string
|
|
|
@@ -414,6 +963,10 @@ class ProcuremenSupplierScore
|
|
|
'price_sum' => (int)round((float)($it['price_sum'] ?? 0)),
|
|
|
'price_weight' => (int)($rule['price_weight'] ?? 50),
|
|
|
'price_score' => (float)($it['price_score'] ?? 0),
|
|
|
+ 'lead_days_sum' => (int)round((float)($it['lead_days_sum'] ?? 0)),
|
|
|
+ 'lead_weight' => (int)($rule['lead_weight'] ?? 0),
|
|
|
+ 'lead_enabled' => ((int)($rule['lead_weight'] ?? 0) > 0) ? 1 : 0,
|
|
|
+ 'lead_score' => (float)($it['lead_score'] ?? 0),
|
|
|
'rule_id' => (int)($rule['id'] ?? 0),
|
|
|
'createtime' => $now,
|
|
|
'updatetime' => $now,
|
|
|
@@ -422,6 +975,15 @@ class ProcuremenSupplierScore
|
|
|
if ($rows !== []) {
|
|
|
Db::table(self::TABLE_SCORE)->insertAll($rows);
|
|
|
}
|
|
|
+ // 订单明细变更后,同步刷新当月月度总分
|
|
|
+ $companies = [];
|
|
|
+ foreach ($rows as $row) {
|
|
|
+ $cn = trim((string)($row['company_name'] ?? ''));
|
|
|
+ if ($cn !== '') {
|
|
|
+ $companies[] = $cn;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ self::syncMonthlyScoresForYm($ym, $companies !== [] ? $companies : null);
|
|
|
} catch (\Throwable $e) {
|
|
|
Log::write('supplier service score save: ' . $e->getMessage(), 'error');
|
|
|
}
|
|
|
@@ -458,16 +1020,36 @@ class ProcuremenSupplierScore
|
|
|
}
|
|
|
$score = (float)($r['score'] ?? 0);
|
|
|
$rank = (int)($r['rank_no'] ?? 0);
|
|
|
+ $qualityScore = (int)round((float)($r['quality_score'] ?? 0));
|
|
|
+ $priceSum = (int)round((float)($r['price_sum'] ?? 0));
|
|
|
+ $priceScore = (float)($r['price_score'] ?? 0);
|
|
|
+ $qw = (int)($r['quality_weight'] ?? 50);
|
|
|
+ $pw = (int)($r['price_weight'] ?? 50);
|
|
|
+ $lw = (int)($r['lead_weight'] ?? 0);
|
|
|
+ $leadEnabled = $lw > 0 ? 1 : 0;
|
|
|
+ $leadSum = (int)round((float)($r['lead_days_sum'] ?? 0));
|
|
|
+ $leadScore = (float)($r['lead_score'] ?? 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 : '',
|
|
|
+ 'company_name' => $cn,
|
|
|
+ 'customer_id' => (int)($r['customer_id'] ?? 0),
|
|
|
+ 'quality_score' => $qualityScore,
|
|
|
+ 'quality_score_text' => (string)$qualityScore,
|
|
|
+ 'quality_weight' => $qw,
|
|
|
+ 'price_sum' => $priceSum,
|
|
|
+ 'price_sum_text' => (string)$priceSum,
|
|
|
+ 'price_weight' => $pw,
|
|
|
+ 'price_score' => $priceScore,
|
|
|
+ 'price_score_text' => self::formatScore($priceScore),
|
|
|
+ 'lead_days_sum' => $leadSum,
|
|
|
+ 'lead_days_sum_text' => $leadEnabled ? (string)$leadSum : '',
|
|
|
+ 'lead_weight' => $lw,
|
|
|
+ 'lead_enabled' => $leadEnabled,
|
|
|
+ 'lead_score' => $leadScore,
|
|
|
+ 'lead_score_text' => $leadEnabled ? self::formatScore($leadScore) : '',
|
|
|
+ 'score' => $score,
|
|
|
+ 'rank_no' => $rank,
|
|
|
+ 'score_text' => self::formatScore($score),
|
|
|
+ 'rank_text' => $rank > 0 ? (string)$rank : '',
|
|
|
];
|
|
|
}
|
|
|
|
|
|
@@ -475,7 +1057,36 @@ class ProcuremenSupplierScore
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 把排名/评分挂到报价组上(开标后可见)
|
|
|
+ * 清空报价组上的评分留痕字段
|
|
|
+ *
|
|
|
+ * @param array<string, mixed> $g
|
|
|
+ */
|
|
|
+ protected static function clearScoreTrailOnGroup(array &$g, int $showFlag = 0): void
|
|
|
+ {
|
|
|
+ $g['service_rank'] = '';
|
|
|
+ $g['service_score'] = '';
|
|
|
+ $g['service_rank_text'] = '';
|
|
|
+ $g['service_score_text'] = '';
|
|
|
+ $g['service_quality_score'] = '';
|
|
|
+ $g['service_quality_score_text'] = '';
|
|
|
+ $g['service_price_sum'] = '';
|
|
|
+ $g['service_price_sum_text'] = '';
|
|
|
+ $g['service_price_score'] = '';
|
|
|
+ $g['service_price_score_text'] = '';
|
|
|
+ $g['service_lead_days_sum'] = '';
|
|
|
+ $g['service_lead_days_sum_text'] = '';
|
|
|
+ $g['service_lead_score'] = '';
|
|
|
+ $g['service_lead_score_text'] = '';
|
|
|
+ $g['service_lead_enabled'] = 0;
|
|
|
+ $g['service_quality_weight'] = '';
|
|
|
+ $g['service_price_weight'] = '';
|
|
|
+ $g['service_lead_weight'] = '';
|
|
|
+ $g['show_service_score'] = $showFlag;
|
|
|
+ $g['show_service_lead_score'] = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 把排名/评分/计算留痕挂到报价组上(开标后可见)
|
|
|
*
|
|
|
* @param array<int, array<string, mixed>> $quoteGroups
|
|
|
* @return array<int, array<string, mixed>>
|
|
|
@@ -487,26 +1098,18 @@ class ProcuremenSupplierScore
|
|
|
if (!is_array($g)) {
|
|
|
continue;
|
|
|
}
|
|
|
- $g['service_rank'] = '';
|
|
|
- $g['service_score'] = '';
|
|
|
- $g['service_rank_text'] = '';
|
|
|
- $g['service_score_text'] = '';
|
|
|
- $g['show_service_score'] = 0;
|
|
|
+ self::clearScoreTrailOnGroup($g, 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) {
|
|
|
- }
|
|
|
+ // 每次按最新规则重算并落库,避免历史错误总分残留
|
|
|
+ $saved = self::calculateForQuoteGroups($quoteGroups);
|
|
|
+ try {
|
|
|
+ self::saveForOrder($ccydh, $quoteGroups);
|
|
|
+ } catch (\Throwable $e) {
|
|
|
}
|
|
|
foreach ($quoteGroups as &$g) {
|
|
|
if (!is_array($g)) {
|
|
|
@@ -518,14 +1121,31 @@ class ProcuremenSupplierScore
|
|
|
$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['service_score_text'] = self::formatScoreDetailText($hit);
|
|
|
+ $qs = round((float)($hit['quality_score'] ?? 0), 2);
|
|
|
+ $ps = (float)($hit['price_score'] ?? 0);
|
|
|
+ $psum = round((float)($hit['price_sum'] ?? 0), 2);
|
|
|
+ $leadEnabled = ((int)($hit['lead_weight'] ?? 0) > 0) ? 1 : 0;
|
|
|
+ $ls = (float)($hit['lead_score'] ?? 0);
|
|
|
+ $lsum = (int)round((float)($hit['lead_days_sum'] ?? 0));
|
|
|
+ $g['service_quality_score'] = $qs;
|
|
|
+ $g['service_quality_score_text'] = (string)($hit['quality_score_text'] ?? $qs);
|
|
|
+ $g['service_price_sum'] = $psum;
|
|
|
+ $g['service_price_sum_text'] = (string)($hit['price_sum_text'] ?? $psum);
|
|
|
+ $g['service_price_score'] = $ps;
|
|
|
+ $g['service_price_score_text'] = (string)($hit['price_score_text'] ?? self::formatScore($ps));
|
|
|
+ $g['service_lead_enabled'] = $leadEnabled;
|
|
|
+ $g['show_service_lead_score'] = $leadEnabled;
|
|
|
+ $g['service_lead_days_sum'] = $lsum;
|
|
|
+ $g['service_lead_days_sum_text'] = $leadEnabled ? (string)($hit['lead_days_sum_text'] ?? $lsum) : '';
|
|
|
+ $g['service_lead_score'] = $ls;
|
|
|
+ $g['service_lead_score_text'] = $leadEnabled ? (string)($hit['lead_score_text'] ?? self::formatScore($ls)) : '';
|
|
|
+ $g['service_quality_weight'] = (int)($hit['quality_weight'] ?? 50);
|
|
|
+ $g['service_price_weight'] = (int)($hit['price_weight'] ?? 50);
|
|
|
+ $g['service_lead_weight'] = (int)($hit['lead_weight'] ?? 0);
|
|
|
$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;
|
|
|
+ self::clearScoreTrailOnGroup($g, 1);
|
|
|
}
|
|
|
}
|
|
|
unset($g);
|