LAPTOP-VT1IP978\suxio 2 долоо хоног өмнө
parent
commit
d8244f365a

+ 2 - 2
README.md

@@ -5,9 +5,9 @@ php artisan sync:drug-report --source=querydrugreport --full --start-time="2025-
 ```
 
 ```bash
-php artisan sync:drug-report --source=querysealdrugreport --full --start-time="2025-03-01 00:00:00"
+php artisan sync:drug-report --source=querysealdrugreport --full --start-time="2025-01-01 00:00:00"
 ```
 
 ```bash
-php artisan sync:drug-report --source=drugreport.opt.history --full --start-time="2025-03-01 00:00:00"
+php artisan sync:drug-report --source=drugreport.opt.history --full --start-time="2025-11-01 00:00:00"
 ```

+ 39 - 0
app/Console/Commands/SyncDrugReportCommand.php

@@ -0,0 +1,39 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Models\DrugReportInfo;
+use App\Services\SyncDrugReportService;
+use Carbon\Carbon;
+use Illuminate\Console\Command;
+
+class SyncDrugReportCommand extends Command
+{
+    protected $signature = 'sync:drug-report {--source= : 来源} {--full : 全量模式} {--start-time= : 开始时间} {--end-time= : 结束时间} {--page=1 : 当前页码} {--page-size=20 : 每页大小}';
+
+    protected $description = '同步药检报告数据';
+
+    public function handle(SyncDrugReportService $service): int
+    {
+        $source = $this->option('source');
+        $isFull = (bool)$this->option('full');
+        $startTime = $this->option('start-time') ?? Carbon::today()->startOfDay();
+        $endTime = $this->option('end-time') ?? ''; // Carbon::now()
+        $page = $this->option('page') ?? 1;
+        $pageSize = $this->option('page-size') ?? 20;
+
+        if (empty($source)) {
+            echo "参数错误,未指定来源[source]", PHP_EOL;
+            return Command::FAILURE;
+        }
+
+        echo $isFull ? "全量同步开始" : "增量同步开始", sprintf('[%d, %s, %s, %d, %d]……', $isFull, $startTime, $endTime, $page, $pageSize), PHP_EOL;
+
+        $count = $service->syncDrugReport($source, $isFull, $startTime, $endTime, $page, $pageSize);
+
+        echo $isFull ? "全量同步结束" : "增量同步结束", ",共处理 {$count} 条记录!", PHP_EOL;
+
+        return Command::SUCCESS;
+    }
+
+}

+ 11 - 1
app/Models/DrugReportInfo.php

@@ -6,6 +6,7 @@ use App\Traits\Filterable;
 use Illuminate\Database\Eloquent\Builder;
 use Illuminate\Database\Eloquent\Factories\HasFactory;
 use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Eloquent\SoftDeletes;
 use Illuminate\Support\Carbon;
 use Illuminate\Support\Facades\DB;
 
@@ -45,13 +46,16 @@ use Illuminate\Support\Facades\DB;
  * @property array|null $opt_raw_data 报告操作原始数据(来源drugreport.opt.history)
  * @property int $is_sign 是否签收(1-未签收、2-已签收)
  * @property int $is_seal 是否盖章(1-未盖章、2-已盖章)
+ * @property int $sync_seal_opt 同步盖章操作
  * @property Carbon $create_time 创建时间
  * @property Carbon $update_time 更新时间
+ * @property Carbon|null $delete_time 删除时间
  * @property-read string $is_seal_text
  * @property-read string $is_sign_text
  * @method static Builder|DrugReportInfo applyFilters(array $filters = [], array $options = [])
  * @method static Builder|DrugReportInfo newModelQuery()
  * @method static Builder|DrugReportInfo newQuery()
+ * @method static Builder|DrugReportInfo onlyTrashed()
  * @method static Builder|DrugReportInfo query()
  * @method static Builder|DrugReportInfo whereAssRefEntId($value)
  * @method static Builder|DrugReportInfo whereBatchNo($value)
@@ -61,6 +65,7 @@ use Illuminate\Support\Facades\DB;
  * @method static Builder|DrugReportInfo whereBillTime($value)
  * @method static Builder|DrugReportInfo whereBillType($value)
  * @method static Builder|DrugReportInfo whereCreateTime($value)
+ * @method static Builder|DrugReportInfo whereDeleteTime($value)
  * @method static Builder|DrugReportInfo whereDrugId($value)
  * @method static Builder|DrugReportInfo whereDrugName($value)
  * @method static Builder|DrugReportInfo whereFromEntName($value)
@@ -87,12 +92,15 @@ use Illuminate\Support\Facades\DB;
  * @method static Builder|DrugReportInfo whereReportV2Id($value)
  * @method static Builder|DrugReportInfo whereSealRawData($value)
  * @method static Builder|DrugReportInfo whereSealReportUrl($value)
+ * @method static Builder|DrugReportInfo whereSyncSealOpt($value)
  * @method static Builder|DrugReportInfo whereUpdateTime($value)
+ * @method static Builder|DrugReportInfo withTrashed()
+ * @method static Builder|DrugReportInfo withoutTrashed()
  * @mixin \Eloquent
  */
 class DrugReportInfo extends Model
 {
-    use HasFactory, Filterable;
+    use HasFactory, Filterable, SoftDeletes;
 
     protected $table = 'drug_report_info';
 
@@ -102,6 +110,8 @@ class DrugReportInfo extends Model
 
     const UPDATED_AT = 'update_time';
 
+    const DELETED_AT = 'delete_time';
+
     protected $casts = [
         'pkg_ratio_list' => 'array',
         'raw_data' => 'array',

+ 387 - 0
app/Services/SyncDrugReportService.php

@@ -0,0 +1,387 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\DrugReportInfo;
+use Carbon\Carbon;
+use Illuminate\Support\Facades\Log;
+
+class SyncDrugReportService
+{
+    /** @var string 查询上游企业的待签收药检报告信息 */
+    const QUERY_DRUG_REPORT = 'querydrugreport';
+
+    /** @var string 查询上传报告信息接口 */
+    const QUERY_SEAL_DRUG_REPORT = 'querysealdrugreport';
+
+    /** @var string 一张网报告操作日志 */
+    const DRUG_REPORT_OPT_HISTORY = 'drugreport.opt.history';
+
+    /** @var Log */
+    protected $log;
+
+    /** @var QueryDrugReportService */
+    protected $queryDrugReportService;
+
+    /**
+     * 检查同步来源
+     * @param string $source
+     * @return bool
+     */
+    protected function checkSource(string $source): bool
+    {
+        return (in_array($source, [
+            self::QUERY_DRUG_REPORT,
+            self::QUERY_SEAL_DRUG_REPORT,
+            self::DRUG_REPORT_OPT_HISTORY]));
+    }
+
+    /**
+     * 根据来源设置日志频道
+     * @param string $source
+     * @return void
+     */
+    protected function setLog(string $source)
+    {
+        if (self::QUERY_SEAL_DRUG_REPORT == $source) {
+            $this->log = Log::channel('sync_seal');
+        } elseif (self::DRUG_REPORT_OPT_HISTORY == $source) {
+            $this->log = Log::channel('sync_opt');
+        } else {
+            $this->log = Log::channel('sync');
+        }
+    }
+
+
+    /**
+     * 分批次同步数据
+     * @param string $source
+     * @param bool $isFull
+     * @param string $startTime
+     * @param string $endTime
+     * @param int $page
+     * @param int $pageSize
+     * @return int
+     */
+    public function syncDrugReport(string $source, bool $isFull, string $startTime, string $endTime = '', int $page = 1, int $pageSize = 20): int
+    {
+        if (!$this->checkSource($source)) {
+            $this->log->error("来源[source]不正确,无法进行同步", [
+                'source' => $source,
+                'params' => [
+                    'is_full' => $isFull,
+                    'start_time' => $startTime,
+                    'end_time' => $endTime,
+                    'page' => $page,
+                    'page_size' => $pageSize,
+                ],
+            ]);
+            return 0;
+        }
+
+        $this->setLog($source);
+
+        $total = 0;
+
+        $this->log->notice($isFull ? "开始全量同步" : "开始增量同步", [
+            'source' => $source,
+            'params' => [
+                'is_full' => $isFull,
+                'start_time' => $startTime,
+                'end_time' => $endTime,
+                'page' => $page,
+                'page_size' => $pageSize,
+            ],
+        ]);
+
+        if (empty($endTime)) {
+            $endTime = Carbon::parse($startTime)->copy()->endOfMonth()->toDateTimeString();
+        }
+
+        $this->queryDrugReportService = app(QueryDrugReportService::class);
+
+        do {
+            $month = Carbon::parse($startTime)->copy()->format('Ym');
+            try {
+                if (Carbon::parse($startTime)->isFuture()) {
+                    $this->log->warning("时间大于当前时间,同步终止", [
+                        'source' => $source,
+                        'params' => [
+                            'is_full' => $isFull,
+                            'start_time' => $startTime,
+                            'end_time' => $endTime,
+                            'page' => $page,
+                            'page_size' => $pageSize,
+                        ]
+                    ]);
+                    break;
+                }
+
+                $this->log->info("批次 {$month}[{$page}] 开始处理", [
+                    'source' => $source,
+                    'params' => [
+                        'is_full' => $isFull,
+                        'start_time' => $startTime,
+                        'end_time' => $endTime,
+                        'page' => $page,
+                        'page_size' => $pageSize,
+                    ]
+                ]);
+
+                // 分批次拉取数据
+                $data = $this->queryDrugReport($source, $startTime, $endTime, $page, $pageSize);
+                if (empty($data)) {
+                    $this->log->warning("批次 {$month}[{$page}] 没有数据", [
+                        'source' => $source,
+                        'params' => [
+                            'is_full' => $isFull,
+                            'start_time' => $startTime,
+                            'end_time' => $endTime,
+                            'page' => $page,
+                            'page_size' => $pageSize,
+                        ]
+                    ]);
+
+                    if ($isFull) {
+                        $next = Carbon::parse($startTime)->copy()->addMonth();
+                        $startTime = $next->startOfMonth()->toDateTimeString();
+                        $endTime = $next->endOfMonth()->toDateTimeString();
+                        $page = 1;
+                        continue;
+                    }
+                    break;
+                }
+
+                // 处理并保存数据
+                $saveCount = $this->saveDrugReport($source, $data);
+                $total += $saveCount;
+
+                $this->log->info("批次 {$month}[{$page}] 处理完成", [
+                    'source' => $source,
+                    'fetch_count' => count($data),
+                    'save_count' => $saveCount,
+                    'total' => $total,
+                ]);
+
+                // 检查是否还有更多数据
+                if (count($data) <> $pageSize) {
+                    if ($isFull) {
+                        $next = Carbon::parse($startTime)->copy()->addMonth();
+                        $startTime = $next->startOfMonth()->toDateTimeString();
+                        $endTime = $next->endOfMonth()->toDateTimeString();
+                        $page = 1;
+                        continue;
+                    }
+                    break;
+                }
+
+                $page++;
+
+                // 避免请求过于频繁
+                sleep(1);
+
+            } catch (\Exception $e) {
+                $this->log->error("批次 {$month}[{$page}] 数据获取失败", [
+                    'source' => $source,
+                    'params' => [
+                        'is_full' => $isFull,
+                        'start_time' => $startTime,
+                        'end_time' => $endTime,
+                        'page' => $page,
+                        'page_size' => $pageSize,
+                    ],
+                    'error' => $e->getMessage()
+                ]);
+//                continue;
+                break;
+            }
+        } while (true);
+
+        $this->log->notice($isFull ? "全量同步完成" : "增量同步完成", [
+            'source' => $source,
+            'total' => $total
+        ]);
+
+        return $total;
+    }
+
+    /**
+     * 查询药检报告数据
+     * @param string $source
+     * @param string $startTime
+     * @param string $endTime
+     * @param int $page
+     * @param int $pageSize
+     * @return array
+     * @throws \Exception
+     */
+    protected function queryDrugReport(string $source, string $startTime, string $endTime = '', int $page = 1, int $pageSize = 20): array
+    {
+        if (self::QUERY_DRUG_REPORT == $source) {
+            return $this->queryDrugReportService->queryDrugReport($startTime, $endTime, $page, $pageSize);
+        }
+
+        if (self::QUERY_SEAL_DRUG_REPORT == $source) {
+            return $this->queryDrugReportService->querySealDrugReport($startTime, $endTime, $page, $pageSize);
+        }
+
+        if (self::DRUG_REPORT_OPT_HISTORY == $source) {
+            $startDate = Carbon::parse($startTime)->copy()->toDateString();
+            $endDate = Carbon::parse($endTime)->copy()->toDateString();
+            return $this->queryDrugReportService->drugReportOptHistory($startDate, $endDate, $page, $pageSize);
+        }
+
+        return [];
+    }
+
+    /**
+     * 处理并保存批次数据
+     * @param string $source
+     * @param array $data
+     * @return int
+     */
+    protected function saveDrugReport(string $source, array $data): int
+    {
+        try {
+            if (self::DRUG_REPORT_OPT_HISTORY == $source) {
+                return $this->processDrugReportOptHistory($data);
+            }
+
+            if (self::QUERY_SEAL_DRUG_REPORT == $source) {
+                $data = $this->correctSealDrugReport($data);
+            } else {
+                $data = $this->correctDrugReport($data);
+            }
+
+            return DrugReportInfo::bulkUpsert($data);
+        } catch (\Throwable $e) {
+            $this->log->error("保存批次数据失败", [
+                'data' => $data,
+                'error' => $e->getMessage()
+            ]);
+            return 0;
+        }
+    }
+
+    /**
+     * 处理【待签收】医药报告批次数据
+     * @param array $data
+     * @return array
+     */
+    protected function correctDrugReport(array $data): array
+    {
+        return array_map(function ($item) {
+            return [
+                'report_id' => $item['drug_report_id'] ?? '',
+                'batch_no' => $item['produce_batch_no'] ?? '',
+                'drug_id' => $item['drug_id'] ?? '',
+                'drug_name' => $item['physic_name'] ?? '',
+                'pkg_spec' => $item['pkg_spec'] ?? '',
+                'prepn_spec' => $item['prepn_spec'] ?? '',
+                'prepn_type_desc' => $item['prepn_type_desc'] ?? '',
+                'bill_type' => $item['bill_type'] ?? 0,
+                'bill_id' => $item['bill_id'] ?? '',
+                'bill_detail_id' => $item['bill_detail_id'] ?? '',
+                'bill_time' => $item['bill_time'] ?? '',
+                'bill_code' => $item['bill_code'] ?? '',
+                'produce_date' => $item['produce_date'] ?? '',
+                'produce_ent_id' => $item['produce_ent_id'] ?? '',
+                'produce_ent_name' => $item['produce_ent_name'] ?? '',
+                'ass_ref_ent_id' => $item['ass_ref_ent_id'] ?? '',
+                'from_ref_ent_id' => $item['from_ref_ent_id'] ?? '',
+                'from_ent_name' => $item['from_ent_name'] ?? '',
+                'report_url' => $item['sealed_report_url'] ?? '',
+//                'file_name' => $item['file_name'] ?? '',
+                'report_sign_status' => $item['drug_report_sign_status'] ?? '',
+                'raw_data' => json_encode($item ?? [], JSON_UNESCAPED_UNICODE),
+            ];
+        }, $data);
+    }
+
+    /**
+     * 处理【已签收】医药报告批次数据
+     * @param array $data
+     * @return array
+     */
+    protected function correctSealDrugReport(array $data): array
+    {
+        return array_map(function ($item) {
+            return [
+                'report_v2_id' => $item['drug_report_v2_id'] ?? '',
+                'report_name' => $item['drug_report_name'] ?? '',
+                'report_no' => $item['report_no'] ?? '',
+                'report_date' => $item['report_date'] ?? '',
+                'batch_no' => $item['batch_no'] ?? '',
+                'drug_id' => $item['drug_ent_base_info_id'] ?? '',
+                'drug_name' => $item['drug_name'] ?? '',
+                'prod_code' => $item['prod_code'] ?? '',
+                'pkg_spec' => $item['pkg_spec'] ?? '',
+                'prepn_spec' => $item['prepn_spec'] ?? '',
+                'pkg_ratio_list' => json_encode($item['pkg_ratio_list'] ?? [], JSON_UNESCAPED_UNICODE),
+                'seal_report_url' => $item['sealed_report_url'] ?? '',
+                'seal_raw_data' => json_encode($item ?? [], JSON_UNESCAPED_UNICODE),
+            ];
+        }, $data);
+    }
+
+    /**
+     * 处理医药报告操作批次数据
+     * @param array $data
+     * @return int
+     */
+    protected function processDrugReportOptHistory(array $data): int
+    {
+        $count = 0;
+
+        foreach ($data as $item) {
+            try {
+                // 操作类型(insertReport:新增报告、deleteReport:删除报告、sealReport:报告签章、updateReport:报告更新)
+                $optType = $item['opt_type'];
+
+                $drugReportInfo = DrugReportInfo::query()->where([
+                    'batch_no' => $item['batch_no'],
+                    'drug_id' => $item['drug_id'],
+                ])->first();
+
+                if (empty($drugReportInfo)) {
+                    $drugReportInfo = DrugReportInfo::create([
+                        'batch_no' => $item['batch_no'],
+                        'drug_id' => $item['drug_id'],
+                        'report_v2_id' => $item['report_v2_id'],
+                        'report_date' => $item['report_date'],
+                        'report_name' => $item['report_name'],
+                        'report_no' => $item['report_no'],
+                    ]);
+                }
+
+                switch ($optType) {
+                    case "deleteReport":
+                        $drugReportInfo->delete();
+                        break;
+                    case "sealReport":
+                        $optTime = Carbon::parse($item['opt_time']);
+                        $updateTime = Carbon::parse($drugReportInfo->update_time);
+                        if ($optTime->gt($updateTime)) {
+                            $drugReportInfo->is_sign = 1;
+                            $drugReportInfo->is_seal = 1;
+                            $drugReportInfo->sync_seal_opt = 1;
+                        }
+                        break;
+                }
+
+                $drugReportInfo->opt_raw_data = json_encode($item, JSON_UNESCAPED_UNICODE);
+                $drugReportInfo->save();
+
+                $count ++;
+            } catch (\Exception $e) {
+                $this->log->error("保存数据项失败", [
+                    'data' => $item,
+                    'error' => $e->getMessage()
+                ]);
+                continue;
+            }
+        }
+
+        return $count;
+    }
+}