完成了基本调整调整

This commit is contained in:
2026-09-18 20:55:21 +08:00
parent 864187e105
commit 57e0854a0e
56 changed files with 3122 additions and 105 deletions
@@ -0,0 +1,200 @@
<?php
// +----------------------------------------------------------------------
// | 企油通 · 企业发票(7.14)
// +----------------------------------------------------------------------
// | 7.14 的粒度是「企业 + 通道 + 月份(+ 司机手机号)」,返回的 invoice 只是一组地址,
// | 和具体订单之间【没有】对应关系。所以:
// | · 表按这个维度存(yoshop_qyt_invoice)
// | · 订单按「订单企业 + 消费月份 + 司机手机号」去关联 —— 同一司机当月多笔订单看到的是同一批发票
// +----------------------------------------------------------------------
declare (strict_types=1);
namespace app\common\service;
use app\common\model\qyt\Enterprise as EnterpriseModel;
use think\facade\Db;
class QytInvoice
{
/** 已同步但暂无发票的记录,超过多久再次点开时自动重新同步(秒) */
const RESYNC_EMPTY_AFTER = 3600;
/**
* 调 7.14 同步某个维度的发票并入库
* @param EnterpriseModel|array $enterprise
* @param string $month YYYY-MM
* @param string $mobile 司机手机号,空串=企业全量
* @return array ['ok'=>bool, 'message'=>string, 'row'=>array]
*/
public static function sync($enterprise, string $month, string $mobile = ''): array
{
if (!preg_match('/^\d{4}-(0[1-9]|1[0-2])$/', $month)) {
return ['ok' => false, 'message' => '月份格式应为 YYYY-MM', 'row' => []];
}
if ($mobile !== '' && !preg_match('/^1[3-9]\d{9}$/', $mobile)) {
return ['ok' => false, 'message' => '司机手机号格式不正确', 'row' => []];
}
$ent = $enterprise instanceof EnterpriseModel ? $enterprise->toArray() : (array) $enterprise;
if (empty($ent['enterprise_id'])) {
return ['ok' => false, 'message' => '企业不存在', 'row' => []];
}
$res = QytOil::forEnterprise($ent)->invoice($month, $mobile);
$key = [
'qyt_enterprise_id' => (int) $ent['enterprise_id'],
'passageway' => (string) $ent['passageway'],
'month' => $month,
'mobile' => $mobile,
];
$now = time();
if ($res['code'] !== '00') {
$message = '同步失败:' . ($res['message'] ?: '未知错误');
self::upsert($key, [
'business_license' => (string) $ent['business_license'],
'sync_status' => 2,
'sync_time' => $now,
'sync_text' => mb_substr($message, 0, 250),
'store_id' => (int) ($ent['store_id'] ?? 0),
]);
return ['ok' => false, 'message' => $message, 'row' => self::format(self::find($key))];
}
$urls = self::normalizeUrls($res['data']['invoice'] ?? []);
$orders = (array) ($res['data']['order_list'] ?? []);
$text = $urls ? '同步成功,共 ' . count($urls) . ' 张发票' : '同步成功,企油通暂未出具该月发票';
self::upsert($key, [
'business_license' => (string) $ent['business_license'],
'invoice_urls' => json_encode($urls, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'invoice_count' => count($urls),
'order_count' => count($orders),
'raw' => mb_substr((string) json_encode(QytOil::maskDeep((array) $res['data']), JSON_UNESCAPED_UNICODE), 0, 1000000),
'sync_status' => 1,
'sync_time' => $now,
'sync_text' => $text,
'store_id' => (int) ($ent['store_id'] ?? 0),
]);
return ['ok' => true, 'message' => $text, 'row' => self::format(self::find($key))];
}
/**
* 取某个订单关联的发票。本地没有、上次同步失败、或「暂无发票」且已超过 1 小时,会自动去同步一次
*/
public static function forOrder(array $order, bool $refresh = false): array
{
$enterprise = EnterpriseModel::detail((int) $order['qyt_enterprise_id']);
if (!$enterprise) {
return ['ok' => false, 'message' => '本单绑定的企油通企业不存在', 'month' => '', 'row' => []];
}
$month = self::orderMonth($order);
$mobile = (string) $order['mobile'];
$key = [
'qyt_enterprise_id' => (int) $enterprise['enterprise_id'],
'passageway' => (string) $enterprise['passageway'],
'month' => $month,
'mobile' => $mobile,
];
$row = self::find($key);
$stale = !$row
|| (int) $row['sync_status'] !== 1
|| ((int) $row['invoice_count'] === 0 && time() - (int) $row['sync_time'] > self::RESYNC_EMPTY_AFTER);
$message = $row ? (string) $row['sync_text'] : '';
if ($refresh || $stale) {
$r = self::sync($enterprise, $month, $mobile);
$message = $r['message'];
$row = self::find($key);
if (!$r['ok'] && !$row) {
return ['ok' => false, 'message' => $message, 'month' => $month, 'row' => []];
}
}
return ['ok' => true, 'message' => $message, 'month' => $month, 'row' => self::format($row)];
}
/**
* 订单的消费月份:优先取加油流水的支付时间,没有再依次退到消费时间 / 发放时间 / 下单时间
*/
public static function orderMonth(array $order): string
{
$t = (int) Db::name('qyt_consume')->where('order_id', $order['order_id'])->max('pay_time');
foreach (['consume_time', 'oil_time', 'create_time'] as $field) {
if ($t > 0) {
break;
}
$t = is_numeric($order[$field] ?? null) ? (int) $order[$field] : (int) strtotime((string) ($order[$field] ?? ''));
}
return date('Y-m', $t > 0 ? $t : time());
}
/**
* 7.14 的 invoice 文档只写了「发票地址集」,没给元素结构。
* 兼容两种形态:直接是 URL 字符串;或对象(取常见的 url 字段,取不到就找第一个 http 开头的值)
*/
public static function normalizeUrls($invoice): array
{
$out = [];
foreach ((array) $invoice as $item) {
$url = '';
if (is_string($item)) {
$url = trim($item);
} elseif (is_array($item)) {
foreach (['url', 'invoice_url', 'file_url', 'pdf_url', 'download_url', 'path'] as $k) {
if (!empty($item[$k]) && is_string($item[$k])) {
$url = trim($item[$k]);
break;
}
}
if ($url === '') {
foreach ($item as $v) {
if (is_string($v) && preg_match('#^https?://#i', trim($v))) {
$url = trim($v);
break;
}
}
}
}
if ($url !== '' && preg_match('#^https?://#i', $url)) {
$out[] = $url;
}
}
return array_values(array_unique($out));
}
/** 列表 / 详情输出格式 */
public static function format($row): array
{
if (!$row) {
return [];
}
$row = (array) $row;
unset($row['raw']);
$urls = json_decode((string) ($row['invoice_urls'] ?? ''), true);
$row['invoice_urls'] = is_array($urls) ? $urls : [];
$row['mobile_mask'] = $row['mobile'] !== '' ? QytOil::maskMobile((string) $row['mobile']) : '企业全量';
$row['sync_time_text'] = !empty($row['sync_time']) ? date('Y-m-d H:i:s', (int) $row['sync_time']) : '';
return $row;
}
protected static function find(array $key): ?array
{
$row = Db::name('qyt_invoice')->where($key)->find();
return $row ?: null;
}
protected static function upsert(array $key, array $data): void
{
$data['update_time'] = time();
if (Db::name('qyt_invoice')->where($key)->update($data)) {
return;
}
try {
Db::name('qyt_invoice')->insert($key + $data + ['create_time' => time()]);
} catch (\Throwable $e) {
// 并发下另一请求刚插入:唯一键冲突,改为更新
if (strpos($e->getMessage(), 'Duplicate') === false) {
throw $e;
}
Db::name('qyt_invoice')->where($key)->update($data);
}
}
}