first commit
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\common\command;
|
||||
|
||||
use app\common\enum\CrontabEnum;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use Cron\CronExpression;
|
||||
use think\facade\Console;
|
||||
use app\common\model\Crontab as CrontabModel;
|
||||
|
||||
/**
|
||||
* 定时任务
|
||||
* Class Crontab
|
||||
* @package app\command
|
||||
*/
|
||||
class Crontab extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('crontab')
|
||||
->setDescription('定时任务');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$lists = CrontabModel::where('status', CrontabEnum::START)->select()->toArray();
|
||||
if (empty($lists)) {
|
||||
return false;
|
||||
}
|
||||
$time = time();
|
||||
foreach ($lists as $item) {
|
||||
if (empty($item['last_time'])) {
|
||||
$lastTime = (new CronExpression($item['expression']))
|
||||
->getNextRunDate()
|
||||
->getTimestamp();
|
||||
CrontabModel::where('id', $item['id'])->update([
|
||||
'last_time' => $lastTime,
|
||||
]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$nextTime = (new CronExpression($item['expression']))
|
||||
->getNextRunDate($item['last_time'])
|
||||
->getTimestamp();
|
||||
if ($nextTime > $time) {
|
||||
// 未到时间,不执行
|
||||
continue;
|
||||
}
|
||||
// 开始执行
|
||||
self::start($item);
|
||||
}
|
||||
}
|
||||
|
||||
public static function start($item)
|
||||
{
|
||||
// 开始执行
|
||||
$startTime = microtime(true);
|
||||
try {
|
||||
$params = explode(' ', $item['params']);
|
||||
if (is_array($params) && !empty($item['params'])) {
|
||||
Console::call($item['command'], $params);
|
||||
} else {
|
||||
Console::call($item['command']);
|
||||
}
|
||||
// 清除错误信息
|
||||
CrontabModel::where('id', $item['id'])->update(['error' => '']);
|
||||
} catch (\Exception $e) {
|
||||
// 记录错误信息
|
||||
CrontabModel::where('id', $item['id'])->update([
|
||||
'error' => $e->getMessage(),
|
||||
'status' => CrontabEnum::ERROR
|
||||
]);
|
||||
} finally {
|
||||
$endTime = microtime(true);
|
||||
// 本次执行时间
|
||||
$useTime = round(($endTime - $startTime), 2);
|
||||
// 最大执行时间
|
||||
$maxTime = max($useTime, $item['max_time']);
|
||||
// 更新最后执行时间
|
||||
CrontabModel::where('id', $item['id'])->update([
|
||||
'last_time' => time(),
|
||||
'time' => $useTime,
|
||||
'max_time' => $maxTime
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\command;
|
||||
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
|
||||
/**
|
||||
* 定时:补全订单 creator_id(逻辑在 SyncWechatWorkBills::fillOrderCreatorsFromPayee)
|
||||
*/
|
||||
class FillOrderCreatorFromPayee extends SyncWechatWorkBills
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('fill_order_creator_from_payee')
|
||||
->setDescription('补全订单创建人:creator_id 为空时按 payee_userid 匹配 zyt_admin.work_wechat_userid');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$fixed = $this->fillOrderCreatorsFromPayee();
|
||||
$output->writeln("补全创建人完成,共更新 {$fixed} 条订单");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | likeadmin快速开发前后端分离管理后台(PHP版)
|
||||
// +----------------------------------------------------------------------
|
||||
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
|
||||
// | 开源版本可自由商用,可去除界面版权logo
|
||||
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
|
||||
// | github下载:https://github.com/likeshop-github/likeadmin
|
||||
// | 访问官网:https://www.likeadmin.cn
|
||||
// | likeadmin团队 版权所有 拥有最终解释权
|
||||
// +----------------------------------------------------------------------
|
||||
// | author: likeadminTeam
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
namespace app\common\command;
|
||||
|
||||
use app\common\enum\PayEnum;
|
||||
use app\common\enum\RefundEnum;
|
||||
use app\common\model\recharge\RechargeOrder;
|
||||
use app\common\model\refund\RefundLog;
|
||||
use app\common\model\refund\RefundRecord;
|
||||
use app\common\service\pay\WeChatPayService;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\Output;
|
||||
use think\facade\Log;
|
||||
|
||||
|
||||
class QueryRefund extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('query_refund')
|
||||
->setDescription('订单退款状态处理');
|
||||
}
|
||||
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
try {
|
||||
// 查找退款中的退款记录(微信,支付宝支付)
|
||||
$refundRecords = (new RefundLog())->alias('l')
|
||||
->join('refund_record r', 'r.id = l.record_id')
|
||||
->field([
|
||||
'l.id' => 'log_id', 'l.sn' => 'log_sn',
|
||||
'r.id' => 'record_id', 'r.order_id', 'r.sn' => 'record_sn', 'r.order_type'
|
||||
])
|
||||
->where(['l.refund_status' => RefundEnum::REFUND_ING])
|
||||
->select()->toArray();
|
||||
|
||||
if (empty($refundRecords)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 分别处理各个类型订单
|
||||
$rechargeRecords = array_filter($refundRecords, function ($item) {
|
||||
return $item['order_type'] == RefundEnum::ORDER_TYPE_RECHARGE;
|
||||
});
|
||||
|
||||
if (!empty($rechargeRecords)) {
|
||||
$this->handleRechargeOrder($rechargeRecords);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
Log::write('订单退款状态查询失败,失败原因:' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 处理充值订单
|
||||
* @param $refundRecords
|
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
|
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
|
||||
* @author 段誉
|
||||
* @date 2023/3/1 15:55
|
||||
*/
|
||||
public function handleRechargeOrder($refundRecords)
|
||||
{
|
||||
$orderIds = array_unique(array_column($refundRecords, 'order_id'));
|
||||
$Orders = RechargeOrder::whereIn('id', $orderIds)->column('*', 'id');
|
||||
|
||||
foreach ($refundRecords as $record) {
|
||||
if (!isset($Orders[$record['order_id']])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$order = $Orders[$record['order_id']];
|
||||
if (!in_array($order['pay_way'], [PayEnum::WECHAT_PAY, PayEnum::ALI_PAY])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->checkReFundStatus([
|
||||
'record_id' => $record['record_id'],
|
||||
'log_id' => $record['log_id'],
|
||||
'log_sn' => $record['log_sn'],
|
||||
'pay_way' => $order['pay_way'],
|
||||
'order_terminal' => $order['order_terminal'],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 校验退款状态
|
||||
* @param $refundData
|
||||
* @return bool
|
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
|
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
|
||||
* @author 段誉
|
||||
* @date 2023/3/1 15:54
|
||||
*/
|
||||
public function checkReFundStatus($refundData)
|
||||
{
|
||||
$result = null;
|
||||
switch ($refundData['pay_way']) {
|
||||
case PayEnum::WECHAT_PAY:
|
||||
$result = self::checkWechatRefund($refundData['order_terminal'], $refundData['log_sn']);
|
||||
break;
|
||||
}
|
||||
|
||||
if (is_null($result)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (true === $result) {
|
||||
$this->updateRefundSuccess($refundData['log_id'], $refundData['record_id']);
|
||||
} else {
|
||||
$this->updateRefundMsg($refundData['log_id'], $result);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 查询微信支付退款状态
|
||||
* @param $orderTerminal
|
||||
* @param $refundLogSn
|
||||
* @return bool|string|null
|
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
|
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
|
||||
* @author 段誉
|
||||
* @date 2023/3/1 15:47
|
||||
*/
|
||||
public function checkWechatRefund($orderTerminal, $refundLogSn)
|
||||
{
|
||||
// 根据商户退款单号查询退款
|
||||
$result = (new WeChatPayService($orderTerminal))->queryRefund($refundLogSn);
|
||||
|
||||
if (!empty($result['status']) && $result['status'] == 'SUCCESS') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!empty($result['code']) || !empty($result['message'])) {
|
||||
return '微信:' . $result['code'] . '-' . $result['message'];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 更新记录为成功
|
||||
* @param $logId
|
||||
* @param $recordId
|
||||
* @author 段誉
|
||||
* @date 2023/3/1 15:38
|
||||
*/
|
||||
public function updateRefundSuccess($logId, $recordId)
|
||||
{
|
||||
// 更新日志
|
||||
RefundLog::update([
|
||||
'id' => $logId,
|
||||
'refund_status' => RefundEnum::REFUND_SUCCESS,
|
||||
]);
|
||||
// 更新记录
|
||||
RefundRecord::update([
|
||||
'id' => $recordId,
|
||||
'refund_status' => RefundEnum::REFUND_SUCCESS,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notes 更新退款信息
|
||||
* @param $logId
|
||||
* @param $msg
|
||||
* @author 段誉
|
||||
* @date 2023/3/1 15:47
|
||||
*/
|
||||
public function updateRefundMsg($logId, $msg)
|
||||
{
|
||||
// 更新日志
|
||||
RefundLog::update([
|
||||
'id' => $logId,
|
||||
'refund_msg' => $msg,
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\command;
|
||||
|
||||
use app\adminapi\logic\tcm\DiagnosisLogic;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
use think\facade\Log;
|
||||
|
||||
/**
|
||||
* 从腾讯云 IM 拉取诊单单聊漫游消息并写入本地归档表(建议 cron 每几小时执行)
|
||||
*/
|
||||
class SyncImChatArchive extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('sync_im_chat_archive')
|
||||
->setDescription('同步诊单腾讯云 IM 聊天记录到数据库归档')
|
||||
->addOption('since-days', null, Option::VALUE_OPTIONAL, '仅处理最近 N 天内更新过的诊单;0 表示不限制', '7')
|
||||
->addOption('limit', 'l', Option::VALUE_OPTIONAL, '本轮最多处理的诊单数量(1-500)', '50')
|
||||
->addOption('diagnosis-id', null, Option::VALUE_OPTIONAL, '只同步指定诊单 ID,设置后忽略 since-days', '0');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$sinceDays = (int)$input->getOption('since-days');
|
||||
$sinceDays = max(0, $sinceDays);
|
||||
$limit = (int)$input->getOption('limit');
|
||||
$onlyId = (int)$input->getOption('diagnosis-id');
|
||||
$only = $onlyId > 0 ? $onlyId : null;
|
||||
|
||||
if ($only !== null) {
|
||||
$output->writeln("同步诊单 ID={$only} ...");
|
||||
} else {
|
||||
$output->writeln('同步 IM 归档:since-days=' . ($sinceDays > 0 ? $sinceDays : '不限制') . ", limit={$limit}");
|
||||
}
|
||||
|
||||
$stats = DiagnosisLogic::syncImChatArchiveBatch($sinceDays, $limit, $only);
|
||||
$output->writeln("处理诊单数: {$stats['diagnoses']},新插入行数(INSERT IGNORE 成功数): {$stats['inserted']}");
|
||||
if (!empty($stats['errors'])) {
|
||||
foreach ($stats['errors'] as $e) {
|
||||
$output->writeln("<error>{$e}</error>");
|
||||
Log::error('sync_im_chat_archive: ' . $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\command;
|
||||
|
||||
use app\common\model\doctor\Medicine;
|
||||
use app\common\service\doctor\MedicineNameAbbrService;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
|
||||
/**
|
||||
* 批量回填药材库 name_pinyin_abbr(拼音首字母,供检索)
|
||||
*
|
||||
* 用法:
|
||||
* php think sync_medicine_pinyin_abbr # 仅更新 abbr 为空的记录
|
||||
* php think sync_medicine_pinyin_abbr --all # 全部按当前名称重算
|
||||
* php think sync_medicine_pinyin_abbr --dry # 只打印将更新多少条,不写库
|
||||
*/
|
||||
class SyncMedicinePinyinAbbr extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('sync_medicine_pinyin_abbr')
|
||||
->setDescription('回填/刷新药材表 name_pinyin_abbr(需已执行迁移并 composer 安装 overtrue/pinyin)')
|
||||
->addOption('all', null, Option::VALUE_NONE, '重算全部记录(默认只处理 abbr 为空的)')
|
||||
->addOption('dry', null, Option::VALUE_NONE, '仅统计/预览,不写入数据库');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
if (!class_exists(\Overtrue\Pinyin\Pinyin::class)) {
|
||||
$output->writeln('<error>未检测到 overtrue/pinyin,请在 server 目录执行: composer update</error>');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$all = (bool) $input->getOption('all');
|
||||
$dry = (bool) $input->getOption('dry');
|
||||
|
||||
$makeQuery = function () use ($all) {
|
||||
$q = Medicine::order('id', 'asc');
|
||||
if (!$all) {
|
||||
$q->where('name_pinyin_abbr', '');
|
||||
}
|
||||
|
||||
return $q;
|
||||
};
|
||||
|
||||
$total = $makeQuery()->count();
|
||||
if ($total === 0) {
|
||||
$output->writeln('没有需要处理的记录。');
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
$output->writeln(($all ? '模式: 全部重算' : '模式: 仅空 abbr') . ",待处理 {$total} 条" . ($dry ? '(dry-run)' : ''));
|
||||
|
||||
$updated = 0;
|
||||
$skipped = 0;
|
||||
|
||||
$makeQuery()->chunk(200, function ($rows) use (&$updated, &$skipped, $dry) {
|
||||
foreach ($rows as $row) {
|
||||
$data = $row instanceof \think\Model ? $row->toArray() : (array) $row;
|
||||
$id = (int) ($data['id'] ?? 0);
|
||||
$name = trim((string) ($data['name'] ?? ''));
|
||||
if ($id <= 0 || $name === '') {
|
||||
++$skipped;
|
||||
continue;
|
||||
}
|
||||
$abbr = MedicineNameAbbrService::build($name);
|
||||
if ($abbr === '') {
|
||||
++$skipped;
|
||||
continue;
|
||||
}
|
||||
if ($dry) {
|
||||
++$updated;
|
||||
continue;
|
||||
}
|
||||
Medicine::where('id', $id)->update([
|
||||
'name_pinyin_abbr' => $abbr,
|
||||
'update_time' => time(),
|
||||
]);
|
||||
++$updated;
|
||||
}
|
||||
});
|
||||
|
||||
if ($dry) {
|
||||
$output->writeln("[预览] 将写入/覆盖拼音首字母: {$updated} 条,跳过(空名或无法生成): {$skipped} 条");
|
||||
} else {
|
||||
$output->writeln("完成: 已更新 {$updated} 条,跳过 {$skipped} 条。");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\command;
|
||||
|
||||
use app\adminapi\logic\order\OrderLogic;
|
||||
use app\common\model\auth\Admin;
|
||||
use app\common\model\Order;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
use think\facade\Log;
|
||||
|
||||
/**
|
||||
* 同步企业微信对外收款到订单
|
||||
* 按天拉取,避免接口时间范围限制
|
||||
*/
|
||||
class SyncWechatWorkBills extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('sync_wechat_work_bills')
|
||||
->setDescription('同步企业微信对外收款到订单(按天拉取)')
|
||||
->addOption('date', 'd', Option::VALUE_OPTIONAL, '指定日期 Y-m-d,默认今天', '')
|
||||
->addOption('days', null, Option::VALUE_OPTIONAL, '拉取最近N天,每天单独请求', '1')
|
||||
->addOption(
|
||||
'fill-order-creators-only',
|
||||
null,
|
||||
Option::VALUE_NONE,
|
||||
'仅补全订单创建人:creator_id 为空时用 payee_userid 匹配 admin.work_wechat_userid(可单独做定时任务)'
|
||||
);
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
if ($input->getOption('fill-order-creators-only')) {
|
||||
$fixed = $this->fillOrderCreatorsFromPayee();
|
||||
$output->writeln("补全创建人完成,共更新 {$fixed} 条订单");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$dateStr = $input->getOption('date');
|
||||
$days = (int)$input->getOption('days');
|
||||
$days = $days > 0 ? min($days, 31) : 1;
|
||||
|
||||
$totalCreated = 0;
|
||||
$totalUpdated = 0;
|
||||
$totalSkipped = 0;
|
||||
$errors = [];
|
||||
|
||||
if ($dateStr) {
|
||||
$dates = [$dateStr];
|
||||
} else {
|
||||
$dates = [];
|
||||
for ($i = 0; $i < $days; $i++) {
|
||||
$dates[] = date('Y-m-d', strtotime("-{$i} days"));
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($dates as $d) {
|
||||
$beginTime = strtotime($d . ' 00:00:00');
|
||||
$endTime = strtotime($d . ' 23:59:59');
|
||||
$output->writeln("同步 {$d} ...");
|
||||
$result = OrderLogic::syncFromWechatWorkBills($beginTime, $endTime);
|
||||
$totalCreated += $result['created'];
|
||||
$totalUpdated += $result['updated'];
|
||||
$totalSkipped += $result['skipped'];
|
||||
if (!empty($result['errors'])) {
|
||||
$errors = array_merge($errors, $result['errors']);
|
||||
}
|
||||
$output->writeln(" -> 新增 {$result['created']} 更新 {$result['updated']} 跳过 {$result['skipped']}");
|
||||
}
|
||||
|
||||
$output->writeln("完成: 共新增 {$totalCreated} 更新 {$totalUpdated} 跳过 {$totalSkipped}");
|
||||
if (!empty($errors)) {
|
||||
foreach ($errors as $e) {
|
||||
$output->writeln("<error>{$e}</error>");
|
||||
Log::error('sync_wechat_work_bills: ' . $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* creator_id 为空的订单,用 payee_userid 匹配管理员 work_wechat_userid 写入 creator_id
|
||||
*/
|
||||
protected function fillOrderCreatorsFromPayee(): int
|
||||
{
|
||||
$fixed = 0;
|
||||
Order::whereRaw('(creator_id IS NULL OR creator_id = 0)')
|
||||
->whereNotNull('payee_userid')
|
||||
->where('payee_userid', '<>', '')
|
||||
->chunk(200, function ($orders) use (&$fixed) {
|
||||
foreach ($orders as $order) {
|
||||
$payee = trim((string) $order->getAttr('payee_userid'));
|
||||
if ($payee === '') {
|
||||
continue;
|
||||
}
|
||||
$admin = Admin::where('work_wechat_userid', $payee)->whereNull('delete_time')->find();
|
||||
if (!$admin) {
|
||||
continue;
|
||||
}
|
||||
$order->creator_id = (int) $admin->getAttr('id');
|
||||
$order->save();
|
||||
$fixed++;
|
||||
}
|
||||
});
|
||||
|
||||
return $fixed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\common\command;
|
||||
|
||||
use app\common\model\doctor\Appointment;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
use think\facade\Db;
|
||||
use think\facade\Log;
|
||||
|
||||
/**
|
||||
* 挂号单状态自动更新
|
||||
* - 已预约(status=1) 且预约时间已过超过 35 分钟 → status=4(已过号)
|
||||
* - 已过号(status=4) 且预约时间已过超过 8 小时 → status=2(已取消)
|
||||
* - 不处理:status=3(已完成)、未到预约时间、距预约未满 35 分钟的单子
|
||||
*/
|
||||
class UpdateAppointmentStatus extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('update_appointment_status')
|
||||
->setDescription('挂号单:已预约过号35分钟→已过号;已过号超8小时→已取消')
|
||||
->addOption('dry', null, Option::VALUE_NONE, '仅预览不执行');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
$dryRun = $input->getOption('dry');
|
||||
$now = time();
|
||||
$table = (new Appointment())->getTable();
|
||||
|
||||
$dtExpr = "CONCAT(appointment_date, ' ', IFNULL(appointment_time, '00:00:00'))";
|
||||
|
||||
// 1. 已预约 → 已过号:预约时间早于「当前 − 35 分钟」
|
||||
$missedWhere = "status = 1 AND {$dtExpr} < DATE_SUB(NOW(), INTERVAL 35 MINUTE)";
|
||||
$missedSql = "UPDATE {$table} SET status = 4, update_time = ? WHERE {$missedWhere}";
|
||||
|
||||
// 2. 已过号 → 已取消:预约时间早于或等于「当前 − 8 小时」
|
||||
$cancelWhere = "status = 4 AND {$dtExpr} <= DATE_SUB(NOW(), INTERVAL 8 HOUR)";
|
||||
$cancelSql = "UPDATE {$table} SET status = 2, update_time = ? WHERE {$cancelWhere}";
|
||||
|
||||
if ($dryRun) {
|
||||
$missedPreview = Db::query("SELECT id FROM {$table} WHERE {$missedWhere}");
|
||||
$cancelPreview = Db::query("SELECT id FROM {$table} WHERE {$cancelWhere}");
|
||||
$output->writeln('[预览] 将改为已过号(1→4): ' . count($missedPreview) . ' 条');
|
||||
$output->writeln('[预览] 将改为已取消(4→2,过号超8小时): ' . count($cancelPreview) . ' 条');
|
||||
$output->writeln('[说明] 真实执行时先执行 1→4,再执行 4→2;同一次内刚由 1 变 4 且已超 8 小时的会再被改为 2。');
|
||||
return;
|
||||
}
|
||||
|
||||
$missedCount = Db::execute($missedSql, [$now]);
|
||||
$cancelCount = Db::execute($cancelSql, [$now]);
|
||||
|
||||
$output->writeln("已过号(1→4): {$missedCount} 条");
|
||||
$output->writeln("已取消(4→2,过号超8小时): {$cancelCount} 条");
|
||||
Log::info("update_appointment_status: 已过号 {$missedCount}, 已取消 {$cancelCount}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user