Compare commits
1 Commits
main
...
feature/qi
| Author | SHA1 | Date | |
|---|---|---|---|
| b3b4846c6e |
67
app/Business/JstOrderLabelPlantingRules.php
Normal file
67
app/Business/JstOrderLabelPlantingRules.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Business;
|
||||
|
||||
use App\Models\Business\JstOrderLabelPlantingRule;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* 聚水潭订单标签明细的「是否种菜订单」判断:按(公司 + 标签)匹配映射表。
|
||||
*
|
||||
* 映射表由财务维护(判断种菜单.xlsx)并按公司缓存,页面与导出共用同一份结果;
|
||||
* 映射表缺失或未导入时不报错,统一按「否」展示。
|
||||
*/
|
||||
class JstOrderLabelPlantingRules
|
||||
{
|
||||
private const TTL_HOURS = 24;
|
||||
|
||||
/** @return array<string,string> [标签 => 是/否] */
|
||||
public function map(int $companyId): array
|
||||
{
|
||||
return Cache::remember($this->key($companyId), now()->addHours(self::TTL_HOURS), function () use ($companyId): array {
|
||||
try {
|
||||
if (! Schema::hasTable('jst_order_label_planting_rules')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$map = [];
|
||||
foreach (JstOrderLabelPlantingRule::query()
|
||||
->where('company_id', $companyId)
|
||||
->get(['label', 'is_planting']) as $row) {
|
||||
$label = trim((string) $row->label);
|
||||
if ($label === '') {
|
||||
continue;
|
||||
}
|
||||
$map[$label] = trim((string) $row->is_planting) === '否' ? '否' : '是';
|
||||
}
|
||||
|
||||
return $map;
|
||||
} catch (Throwable) {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 单个标签的判断结果:命中映射表取映射值(默认是),未命中为否。 */
|
||||
public function flag(int $companyId, ?string $label): string
|
||||
{
|
||||
$key = trim((string) $label);
|
||||
if ($key === '') {
|
||||
return '否';
|
||||
}
|
||||
|
||||
return $this->map($companyId)[$key] ?? '否';
|
||||
}
|
||||
|
||||
public function forget(int $companyId): void
|
||||
{
|
||||
Cache::forget($this->key($companyId));
|
||||
}
|
||||
|
||||
private function key(int $companyId): string
|
||||
{
|
||||
return "jst-order-labels:planting-rules:v1:{$companyId}";
|
||||
}
|
||||
}
|
||||
146
app/Console/Commands/ImportJstOrderLabelPlantingRules.php
Normal file
146
app/Console/Commands/ImportJstOrderLabelPlantingRules.php
Normal file
@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Business\JstOrderLabelPlantingRules;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use PhpOffice\PhpSpreadsheet\IOFactory;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* 导入「标签 | 是否种菜订单」映射表,供聚水潭订单标签明细的「是否种菜订单」列使用。
|
||||
*/
|
||||
class ImportJstOrderLabelPlantingRules extends Command
|
||||
{
|
||||
protected $signature = 'jst-order-labels:import-planting-rules
|
||||
{path : xlsx/csv 文件}
|
||||
{--company= : 公司ID(默认使用当前公司或 1)}
|
||||
{--sheet=1 : 工作表序号(从 1 开始)或名称}
|
||||
{--replace : 先清空该公司已有映射再导入(默认按标签覆盖)}';
|
||||
|
||||
protected $description = '导入聚水潭订单标签明细的「是否种菜订单」映射表';
|
||||
|
||||
public function handle(JstOrderLabelPlantingRules $rules): int
|
||||
{
|
||||
$path = (string) $this->argument('path');
|
||||
if (! is_file($path)) {
|
||||
$this->error('文件不存在:' . $path);
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$companyId = (int) ($this->option('company') ?: (function_exists('company_id') && company_id() ? company_id() : 1));
|
||||
if ($companyId <= 0) {
|
||||
$this->error('公司ID无效,请用 --company 指定。');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
try {
|
||||
$rows = $this->readRows($path);
|
||||
} catch (Throwable $e) {
|
||||
$this->error('读取失败:' . $e->getMessage());
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
if ($rows === []) {
|
||||
$this->error('没有解析到有效数据(需要表头:标签 / 是否种菜订单)。');
|
||||
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$now = now();
|
||||
DB::transaction(function () use ($rows, $now, $companyId): void {
|
||||
if ($this->option('replace')) {
|
||||
DB::table('jst_order_label_planting_rules')->where('company_id', $companyId)->delete();
|
||||
}
|
||||
|
||||
foreach (array_chunk($rows, 500) as $chunk) {
|
||||
DB::table('jst_order_label_planting_rules')->upsert(
|
||||
array_map(static fn (array $row): array => $row + [
|
||||
'company_id' => $companyId,
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
], $chunk),
|
||||
['company_id', 'label'],
|
||||
['is_planting', 'updated_at'],
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
$rules->forget($companyId);
|
||||
|
||||
$planting = count(array_filter($rows, static fn (array $row): bool => $row['is_planting'] === '是'));
|
||||
|
||||
$this->info(sprintf('已导入 %d 条「是否种菜订单」映射(公司 %d):种菜 %d 条,非种菜 %d 条。', count($rows), $companyId, $planting, count($rows) - $planting));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{label:string,is_planting:string}>
|
||||
*/
|
||||
private function readRows(string $path): array
|
||||
{
|
||||
$extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));
|
||||
$readerType = match ($extension) {
|
||||
'xlsx' => 'Xlsx',
|
||||
'xls' => 'Xls',
|
||||
'csv' => 'Csv',
|
||||
default => throw new \InvalidArgumentException('只支持 xlsx / xls / csv 文件。'),
|
||||
};
|
||||
|
||||
$reader = IOFactory::createReader($readerType);
|
||||
$reader->setReadDataOnly(true);
|
||||
if ($readerType === 'Csv') {
|
||||
$reader->setInputEncoding('UTF-8');
|
||||
}
|
||||
|
||||
$book = $reader->load($path);
|
||||
$sheetOption = trim((string) $this->option('sheet'));
|
||||
if ($sheetOption !== '' && ctype_digit($sheetOption)) {
|
||||
$index = (int) $sheetOption - 1;
|
||||
if ($index < 0 || $index >= $book->getSheetCount()) {
|
||||
throw new \InvalidArgumentException('工作表序号超出范围:' . $sheetOption);
|
||||
}
|
||||
$sheet = $book->getSheet($index);
|
||||
} else {
|
||||
$sheet = $sheetOption === '' ? $book->getActiveSheet() : $book->getSheetByName($sheetOption);
|
||||
if ($sheet === null) {
|
||||
throw new \InvalidArgumentException('找不到工作表:' . $sheetOption);
|
||||
}
|
||||
}
|
||||
|
||||
$rows = [];
|
||||
$header = null;
|
||||
foreach ($sheet->getRowIterator() as $row) {
|
||||
$cells = [];
|
||||
$cellIterator = $row->getCellIterator();
|
||||
$cellIterator->setIterateOnlyExistingCells(false);
|
||||
foreach ($cellIterator as $cell) {
|
||||
$cells[] = trim((string) $cell->getValue());
|
||||
}
|
||||
|
||||
if ($header === null) {
|
||||
$cells[0] = preg_replace('/^\xEF\xBB\xBF/', '', $cells[0] ?? '');
|
||||
$header = $cells;
|
||||
continue;
|
||||
}
|
||||
|
||||
$label = trim($cells[0] ?? '');
|
||||
$flag = trim($cells[1] ?? '');
|
||||
if ($label === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rows[] = [
|
||||
'label' => mb_substr($label, 0, 191),
|
||||
'is_planting' => in_array(mb_strtolower($flag), ['否', 'n', 'no', '0', 'false'], true) ? '否' : '是',
|
||||
];
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
}
|
||||
@ -1823,6 +1823,14 @@ class Pages extends Controller
|
||||
['path' => $request->url(), 'query' => $request->query()],
|
||||
);
|
||||
|
||||
// 「是否种菜订单」:按(公司 + 标签)匹配财务维护的映射表,命中为是、未命中为否
|
||||
$plantingRules = app(\App\Business\JstOrderLabelPlantingRules::class);
|
||||
$records->setCollection($records->getCollection()->map(static function ($record) use ($plantingRules) {
|
||||
$record->setAttribute('is_planting_order', $plantingRules->flag((int) company_id(), $record->label));
|
||||
|
||||
return $record;
|
||||
}));
|
||||
|
||||
return [
|
||||
'view' => 'business.pages.billing-transactions.jst-order-labels',
|
||||
'records' => $records,
|
||||
@ -1900,21 +1908,24 @@ class Pages extends Controller
|
||||
public function exportJstOrderLabels(Request $request): StreamedResponse
|
||||
{
|
||||
$query = $this->jstOrderLabelQuery($this->jstOrderLabelFilters($request));
|
||||
$plantingRules = app(\App\Business\JstOrderLabelPlantingRules::class);
|
||||
$companyId = (int) company_id();
|
||||
|
||||
return response()->streamDownload(function () use ($query): void {
|
||||
return response()->streamDownload(function () use ($query, $plantingRules, $companyId): void {
|
||||
$csv = fopen('php://output', 'w');
|
||||
if ($csv === false) {
|
||||
throw new \RuntimeException('无法创建聚水潭订单标签明细导出流。');
|
||||
}
|
||||
fputs($csv, chr(0xEF) . chr(0xBB) . chr(0xBF));
|
||||
fputcsv($csv, ['线上订单号', '店铺名称', '下单时间', '标签']);
|
||||
fputcsv($csv, ['线上订单号', '店铺名称', '下单时间', '是否种菜订单', '标签']);
|
||||
|
||||
$query->orderBy('id')->chunkById(2000, function ($rows) use ($csv): void {
|
||||
$query->orderBy('id')->chunkById(2000, function ($rows) use ($csv, $plantingRules, $companyId): void {
|
||||
foreach ($rows as $row) {
|
||||
fputcsv($csv, [
|
||||
$row->order_no,
|
||||
$row->shop_name,
|
||||
$row->ordered_at?->format('Y-m-d H:i:s'),
|
||||
$plantingRules->flag($companyId, $row->label),
|
||||
$row->label,
|
||||
]);
|
||||
}
|
||||
|
||||
19
app/Models/Business/JstOrderLabelPlantingRule.php
Normal file
19
app/Models/Business/JstOrderLabelPlantingRule.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Business;
|
||||
|
||||
use App\Abstracts\Model;
|
||||
|
||||
/**
|
||||
* 聚水潭订单标签明细 › 「是否种菜订单」映射规则(标签 → 是/否)。
|
||||
*/
|
||||
class JstOrderLabelPlantingRule extends Model
|
||||
{
|
||||
protected $table = 'jst_order_label_planting_rules';
|
||||
|
||||
protected $fillable = [
|
||||
'company_id',
|
||||
'label',
|
||||
'is_planting',
|
||||
];
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
/**
|
||||
* 聚水潭订单标签明细「是否种菜订单」映射表(标签 → 是/否)。
|
||||
*
|
||||
* 来源:财务维护的「判断种菜单.xlsx」(标签 / 是否种菜订单),由
|
||||
* jst-order-labels:import-planting-rules 导入;页面只读匹配,不改动订单标签数据。
|
||||
*/
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('jst_order_label_planting_rules', function (Blueprint $table): void {
|
||||
$table->bigIncrements('id');
|
||||
$table->unsignedInteger('company_id');
|
||||
$table->string('label', 191)->comment('聚水潭订单标签');
|
||||
$table->string('is_planting', 8)->default('是')->comment('是否种菜订单:是/否');
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(['company_id', 'label'], 'jst_order_label_planting_unique');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('jst_order_label_planting_rules');
|
||||
}
|
||||
};
|
||||
@ -125,6 +125,7 @@
|
||||
<th scope="col">线上订单号</th>
|
||||
<th scope="col">店铺名称</th>
|
||||
<th scope="col">下单时间</th>
|
||||
<th scope="col">是否种菜订单</th>
|
||||
<th scope="col">标签</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -135,11 +136,12 @@
|
||||
<td>{{ $record->order_no ?: '-' }}</td>
|
||||
<td>{{ $record->shop_name ?: '-' }}</td>
|
||||
<td>{{ $record->ordered_at?->format('Y-m-d H:i:s') ?? '-' }}</td>
|
||||
<td>{{ $record->is_planting_order ?? '否' }}</td>
|
||||
<td>{{ $record->label ?: '-' }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr class="is-empty">
|
||||
<td colspan="5">
|
||||
<td colspan="6">
|
||||
{{ $totalRows === 0 ? '暂无数据,请先用 jst-order-labels:import 导入订单标签明细。' : '当前筛选条件下没有记录,请调整筛选条件后重试。' }}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user