Supplierservicescore.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. <?php
  2. namespace app\admin\controller;
  3. use app\common\controller\Backend;
  4. use app\common\library\ProcuremenSupplierScore;
  5. use PhpOffice\PhpSpreadsheet\Spreadsheet;
  6. use PhpOffice\PhpSpreadsheet\Style\Alignment;
  7. use PhpOffice\PhpSpreadsheet\Style\Border;
  8. use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
  9. use think\Db;
  10. /**
  11. * 供应商服务评分表(按月汇总供应商总分)
  12. *
  13. * @icon fa fa-trophy
  14. */
  15. class Supplierservicescore extends Backend
  16. {
  17. /** @var \app\admin\model\Supplierservicescore */
  18. protected $model = null;
  19. protected $searchFields = 'company_name';
  20. /** 有列表权限即可导出/批量保存最终得分 */
  21. protected $noNeedRight = ['export', 'savefinalbatch'];
  22. public function _initialize()
  23. {
  24. parent::_initialize();
  25. ProcuremenSupplierScore::ensureSchema();
  26. $this->model = new \app\admin\model\Supplierservicescore;
  27. }
  28. public function index()
  29. {
  30. $this->request->filter(['strip_tags', 'trim']);
  31. if ($this->request->isAjax()) {
  32. $ym = $this->resolveRequestYm();
  33. $keyword = $this->resolveSearchKeyword();
  34. $offset = max(0, (int)$this->request->get('offset', 0));
  35. $limit = (int)$this->request->get('limit', 50);
  36. if ($limit <= 0) {
  37. $limit = 50;
  38. }
  39. $list = $this->loadMonthlySupplierScoreList($ym, $keyword);
  40. $total = count($list);
  41. $pageRows = array_slice($list, $offset, $limit);
  42. $seqBase = $offset;
  43. $rows = [];
  44. foreach ($pageRows as $i => $item) {
  45. $rows[] = [
  46. 'id' => $seqBase + $i + 1,
  47. 'seq_no' => $seqBase + $i + 1,
  48. 'company_name' => (string)($item['company_name'] ?? ''),
  49. 'score' => (float)($item['score'] ?? 0),
  50. 'score_text' => (string)($item['score_text'] ?? ''),
  51. 'final_score' => $item['final_score'] ?? null,
  52. 'final_score_text' => (string)($item['final_score_text'] ?? ''),
  53. 'final_score_saved' => !empty($item['final_score_saved']) ? 1 : 0,
  54. 'rank_no' => (int)($item['rank_no'] ?? 0),
  55. 'ym' => $ym,
  56. ];
  57. }
  58. return json(['total' => $total, 'rows' => $rows]);
  59. }
  60. return $this->view->fetch();
  61. }
  62. /**
  63. * 批量保存当月最终得分
  64. * POST: ym, rows=[{company_name, final_score}, ...] 或 rows_json
  65. */
  66. public function savefinalbatch()
  67. {
  68. if (!$this->auth->check('supplierservicescore/index') && !$this->auth->isSuperAdmin()) {
  69. $this->error(__('You have no permission'));
  70. }
  71. ProcuremenSupplierScore::ensureFinalScoreTable();
  72. $ym = trim((string)$this->request->post('ym', ''));
  73. if ($ym === '') {
  74. $ym = trim((string)$this->request->param('ym', ''));
  75. }
  76. if (!preg_match('/^\d{4}-\d{2}$/', $ym)) {
  77. $this->error('请选择查询月份');
  78. }
  79. $rows = $this->request->post('rows/a', []);
  80. if (!is_array($rows) || $rows === []) {
  81. $rowsJson = trim((string)$this->request->post('rows_json', ''));
  82. if ($rowsJson !== '') {
  83. $decoded = json_decode($rowsJson, true);
  84. if (is_array($decoded)) {
  85. $rows = $decoded;
  86. }
  87. }
  88. }
  89. if (!is_array($rows) || $rows === []) {
  90. $this->error('没有可保存的数据');
  91. }
  92. $ret = ProcuremenSupplierScore::saveFinalScoresForYm($ym, $rows);
  93. $done = (int)($ret['done'] ?? 0);
  94. if ($done < 1) {
  95. $err = trim((string)($ret['error'] ?? ''));
  96. $this->error($err !== '' ? $err : '保存失败,请检查填写的最终得分');
  97. }
  98. $this->success('操作成功');
  99. }
  100. /**
  101. * 导出供应商评审表:按月份汇总供应商
  102. * 列:序号、供应商名称、总分、排名
  103. * 总分优先取最终得分,无则用系统总分;排名同此规则
  104. */
  105. public function export()
  106. {
  107. if (!$this->auth->check('supplierservicescore/index') && !$this->auth->isSuperAdmin()) {
  108. $this->error(__('You have no permission'));
  109. }
  110. $ym = trim((string)$this->request->param('ym', ''));
  111. if (!preg_match('/^(\d{4})-(\d{2})$/', $ym, $m)) {
  112. $this->error('请选择查询月份');
  113. }
  114. $year = (int)$m[1];
  115. $month = (int)$m[2];
  116. if ($month < 1 || $month > 12) {
  117. $this->error('查询月份无效');
  118. }
  119. $list = $this->loadMonthlySupplierScoreList($ym, '');
  120. $title = $year . '年' . $month . '月外协加工供应商评审表';
  121. $spreadsheet = new Spreadsheet();
  122. $sheet = $spreadsheet->getActiveSheet();
  123. $sheet->setTitle('供应商评审表');
  124. $fontName = '宋体';
  125. $borderStyle = [
  126. 'borders' => [
  127. 'allBorders' => [
  128. 'borderStyle' => Border::BORDER_THIN,
  129. 'color' => ['rgb' => '000000'],
  130. ],
  131. ],
  132. ];
  133. $centerAlign = [
  134. 'horizontal' => Alignment::HORIZONTAL_CENTER,
  135. 'vertical' => Alignment::VERTICAL_CENTER,
  136. 'wrapText' => true,
  137. ];
  138. $sheet->mergeCells('A1:D1');
  139. $sheet->setCellValue('A1', $title);
  140. $sheet->getRowDimension(1)->setRowHeight(36);
  141. $sheet->getStyle('A1')->getFont()->setName($fontName)->setBold(true)->setSize(20);
  142. $sheet->getStyle('A1')->getAlignment()->applyFromArray($centerAlign);
  143. $sheet->setCellValue('A2', '序号');
  144. $sheet->setCellValue('B2', '供应商名称');
  145. $sheet->setCellValue('C2', '总分');
  146. $sheet->setCellValue('D2', '排名');
  147. $sheet->getRowDimension(2)->setRowHeight(26);
  148. $sheet->getStyle('A2:D2')->getFont()->setName($fontName)->setBold(true)->setSize(14);
  149. $sheet->getStyle('A2:D2')->getAlignment()->applyFromArray($centerAlign);
  150. $rowNum = 3;
  151. if ($list === []) {
  152. $sheet->mergeCells('A3:D3');
  153. $sheet->setCellValue('A3', '(该月暂无供应商服务评分数据)');
  154. $sheet->getRowDimension(3)->setRowHeight(26);
  155. $sheet->getStyle('A3')->getFont()->setName($fontName)->setSize(12);
  156. $sheet->getStyle('A3')->getAlignment()->applyFromArray($centerAlign);
  157. $rowNum = 4;
  158. } else {
  159. $i = 1;
  160. foreach ($list as $item) {
  161. $scoreText = (string)($item['export_score_text'] ?? $item['score_text'] ?? '');
  162. $sheet->setCellValue('A' . $rowNum, $i);
  163. $sheet->setCellValue('B' . $rowNum, (string)($item['company_name'] ?? ''));
  164. $sheet->setCellValue('C' . $rowNum, $scoreText);
  165. $sheet->setCellValue('D' . $rowNum, (int)($item['rank_no'] ?? $i));
  166. $sheet->getRowDimension($rowNum)->setRowHeight(24);
  167. $rowNum++;
  168. $i++;
  169. }
  170. }
  171. $lastRow = max(2, $rowNum - 1);
  172. $sheet->getStyle('A1:D' . $lastRow)->applyFromArray($borderStyle);
  173. $sheet->getStyle('A3:D' . $lastRow)->getFont()->setName($fontName)->setSize(12);
  174. $sheet->getStyle('A3:D' . $lastRow)->getAlignment()->applyFromArray($centerAlign);
  175. $sheet->getColumnDimension('A')->setWidth(12);
  176. $sheet->getColumnDimension('B')->setWidth(48);
  177. $sheet->getColumnDimension('C')->setWidth(14);
  178. $sheet->getColumnDimension('D')->setWidth(12);
  179. $sheet->getPageSetup()->setFitToPage(true)->setFitToWidth(1)->setFitToHeight(0);
  180. $filename = $title . '.xlsx';
  181. header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
  182. header('Content-Disposition: attachment;filename="' . rawurlencode($filename) . '"');
  183. header('Cache-Control: max-age=0');
  184. $writer = new Xlsx($spreadsheet);
  185. $writer->save('php://output');
  186. $spreadsheet->disconnectWorksheets();
  187. unset($spreadsheet);
  188. exit;
  189. }
  190. /**
  191. * 按月汇总:读月度汇总表(订单评分后自动同步总分;最终得分人工可改)
  192. * 排名:有最终得分按最终得分,否则按系统总分
  193. *
  194. * @return array<int, array<string, mixed>>
  195. */
  196. protected function loadMonthlySupplierScoreList(string $ym, string $keyword = ''): array
  197. {
  198. $ym = trim($ym);
  199. if (!preg_match('/^\d{4}-\d{2}$/', $ym)) {
  200. return [];
  201. }
  202. ProcuremenSupplierScore::ensureSchema();
  203. $monthlyMap = ProcuremenSupplierScore::loadMonthlyMapByYm($ym);
  204. $keyword = trim($keyword);
  205. $list = [];
  206. foreach ($monthlyMap as $name => $item) {
  207. if ($keyword !== '' && mb_stripos($name, $keyword) === false) {
  208. continue;
  209. }
  210. $avg = (float)($item['score'] ?? 0);
  211. $hasFinal = !empty($item['final_saved']);
  212. $final = $hasFinal ? (float)($item['final_score'] ?? 0) : null;
  213. $rankScore = $hasFinal ? (float)$final : $avg;
  214. $list[] = [
  215. 'company_name' => $name,
  216. 'score' => $avg,
  217. 'score_text' => ProcuremenSupplierScore::formatScore($avg),
  218. 'final_score' => $final,
  219. 'final_score_text' => $hasFinal ? ProcuremenSupplierScore::formatScore($final) : '',
  220. 'final_score_saved' => $hasFinal ? 1 : 0,
  221. 'rank_score' => $rankScore,
  222. 'export_score_text' => ProcuremenSupplierScore::formatScore($rankScore),
  223. 'rank_no' => 0,
  224. ];
  225. }
  226. usort($list, function ($a, $b) {
  227. $sa = (float)($a['rank_score'] ?? 0);
  228. $sb = (float)($b['rank_score'] ?? 0);
  229. if ($sa !== $sb) {
  230. return $sb <=> $sa;
  231. }
  232. return strcmp((string)($a['company_name'] ?? ''), (string)($b['company_name'] ?? ''));
  233. });
  234. $rank = 0;
  235. foreach ($list as &$item) {
  236. $rank++;
  237. $item['rank_no'] = $rank;
  238. }
  239. unset($item);
  240. return $list;
  241. }
  242. protected function resolveRequestYm(): string
  243. {
  244. $ym = trim((string)$this->request->get('ym', ''));
  245. if (preg_match('/^\d{4}-\d{2}$/', $ym)) {
  246. return $ym;
  247. }
  248. $filterRaw = $this->request->get('filter', '');
  249. if (is_string($filterRaw) && $filterRaw !== '') {
  250. $filter = json_decode($filterRaw, true);
  251. if (is_array($filter)) {
  252. $fy = trim((string)($filter['ym'] ?? ''));
  253. if (preg_match('/^\d{4}-\d{2}$/', $fy)) {
  254. return $fy;
  255. }
  256. }
  257. }
  258. if (preg_match('/^(\d{4})\D+(\d{1,2})/', $ym, $m)) {
  259. return sprintf('%04d-%02d', (int)$m[1], (int)$m[2]);
  260. }
  261. return date('Y-m');
  262. }
  263. protected function resolveSearchKeyword(): string
  264. {
  265. $keyword = trim((string)$this->request->get('search', ''));
  266. if ($keyword !== '') {
  267. return $keyword;
  268. }
  269. $filterRaw = $this->request->get('filter', '');
  270. if (is_string($filterRaw) && $filterRaw !== '') {
  271. $filter = json_decode($filterRaw, true);
  272. if (is_array($filter)) {
  273. return trim((string)($filter['company_name'] ?? ''));
  274. }
  275. }
  276. return '';
  277. }
  278. }