WaterLedger.php 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. <?php
  2. declare(strict_types=1);
  3. namespace plugin\saiadmin\app\service;
  4. use OpenSpout\Common\Entity\Row;
  5. use OpenSpout\Writer\XLSX\Writer;
  6. use plugin\saiadmin\exception\ApiException;
  7. use plugin\saiadmin\app\model\water\WaterModel;
  8. use plugin\saiadmin\app\model\water\WaterLedger as WaterLedgerModel;
  9. /** 库存流水列表与导出共用筛选口径。 */
  10. class WaterLedger
  11. {
  12. private const KINDS = [
  13. 'inbound'=>'仓库入库', 'warehouse_count'=>'仓库盘点', 'site_count'=>'现场盘点',
  14. 'exchange'=>'满桶换空桶', 'reserve'=>'配送锁定', 'assign'=>'配送派单',
  15. 'depart'=>'配送出库', 'complete'=>'配送签收', 'cancel'=>'取消配送',
  16. 'partial_receive'=>'部分签收/拒收', 'delivery_return'=>'退回入库/破损隔离', 'route_settle'=>'返仓空桶交接',
  17. 'promotion_reward'=>'推广赠水权益', 'promotion_reject'=>'推广奖励驳回', 'promotion_policy'=>'推广规则修改',
  18. ];
  19. private function query(array $filters)
  20. {
  21. $keyword = trim((string)($filters['keyword'] ?? ''));
  22. if (mb_strlen($keyword) > 120) throw new ApiException('搜索内容不能超过 120 个字符');
  23. $kind = (string)($filters['kind'] ?? '');
  24. if ($kind !== '' && !isset(self::KINDS[$kind])) throw new ApiException('操作类型不支持');
  25. $dates = [];
  26. foreach (['start_date','end_date'] as $key) {
  27. $value = (string)($filters[$key] ?? '');
  28. if ($value !== '') {
  29. $date = \DateTimeImmutable::createFromFormat('!Y-m-d', $value);
  30. if (!$date || $date->format('Y-m-d') !== $value) throw new ApiException('日期格式不正确');
  31. }
  32. $dates[$key] = $value;
  33. }
  34. if ($dates['start_date'] !== '' && $dates['end_date'] !== '' && $dates['start_date'] > $dates['end_date']) {
  35. throw new ApiException('开始日期不能晚于结束日期');
  36. }
  37. $query = WaterLedgerModel::query()->alias('l')
  38. ->leftJoin('sa_water_enterprise as e','e.id','=','l.enterprise_id')
  39. ->leftJoin('sa_water_sku as s','s.id','=','l.sku_id')
  40. ->whereIn('l.kind',array_keys(self::KINDS))
  41. ->selectRaw('l.id,l.kind,l.quantity,l.reference,l.note,l.created_at,e.name AS enterprise_name,s.name AS sku_name');
  42. if ($keyword !== '') $query->matching('l.reference|l.note|e.name|s.name','%'.$keyword.'%');
  43. if ($kind !== '') $query->where('l.kind',$kind);
  44. if ($dates['start_date'] !== '') $query->where('l.created_at','>=',$dates['start_date'].' 00:00:00');
  45. if ($dates['end_date'] !== '') $query->where('l.created_at','<=',$dates['end_date'].' 23:59:59');
  46. return $query;
  47. }
  48. public function listing(array $filters): array
  49. {
  50. $result = $this->query($filters)->orderBy('l.id','desc')->pageResult(max(1,(int)($filters['page'] ?? 1)),max(1,min(100,(int)($filters['limit'] ?? 10))));
  51. foreach ($result['data'] as &$row) $row['kind_label'] = self::KINDS[$row['kind']];
  52. unset($row);
  53. $result['kinds'] = [];
  54. foreach (self::KINDS as $value=>$label) $result['kinds'][] = compact('value','label');
  55. return $result;
  56. }
  57. /** 返回 XLSX 内容,临时文件不在 Web 目录内,读取后立即删除。 */
  58. public function export(array $filters): string
  59. {
  60. // 多取一条用于检测上限;不受列表 page/limit 影响,也不会静默截断。
  61. $rows = $this->query($filters)->orderBy('l.id','desc')->limit(50001)->get()->toArray();
  62. if (count($rows) > 50000) throw new ApiException('导出记录超过 50000 条,请缩小筛选范围');
  63. $path = tempnam(sys_get_temp_dir(),'water-ledger-');
  64. if ($path === false) throw new ApiException('无法创建导出文件,请稍后重试');
  65. $writer = new Writer();
  66. $opened = false;
  67. try {
  68. $writer->openToFile($path);
  69. $opened = true;
  70. $writer->addRow(Row::fromValues(['流水编号','操作类型','商品规格','企业','数量 / 桶','关联凭据','说明','时间']));
  71. foreach ($rows as $row) {
  72. $writer->addRow(Row::fromValues([
  73. (int)$row['id'], self::KINDS[$row['kind']], self::excelText($row['sku_name']),
  74. self::excelText($row['enterprise_name']), (int)$row['quantity'],
  75. self::excelText($row['reference']), self::excelText($row['note']), (string)$row['created_at'],
  76. ]));
  77. }
  78. $writer->close();
  79. $opened = false;
  80. $content = file_get_contents($path);
  81. if ($content === false) throw new ApiException('读取导出文件失败');
  82. return $content;
  83. } finally {
  84. try { if ($opened) $writer->close(); }
  85. finally { if (is_file($path)) unlink($path); }
  86. }
  87. }
  88. private static function excelText($value): string
  89. {
  90. $text = (string)($value ?? '');
  91. return preg_match('/^[=+@\-\t\r\n]/u',$text) ? "'".$text : $text;
  92. }
  93. }