mirror of
https://github.com/crmeb/CRMEB.git
synced 2026-10-28 13:31:04 +00:00
564 lines
23 KiB
PHP
564 lines
23 KiB
PHP
<?php
|
||
// +----------------------------------------------------------------------
|
||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||
// +----------------------------------------------------------------------
|
||
// | Copyright (c) 2016~2026 https://www.crmeb.com All rights reserved.
|
||
// +----------------------------------------------------------------------
|
||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||
// +----------------------------------------------------------------------
|
||
// | Author: CRMEB Team <admin@crmeb.com>
|
||
// +----------------------------------------------------------------------
|
||
declare (strict_types=1);
|
||
|
||
namespace app\services\order;
|
||
|
||
|
||
use app\jobs\OrderInvoiceJob;
|
||
use crmeb\basic\BaseService;
|
||
use crmeb\exceptions\AdminException;
|
||
use app\dao\order\OrderCenterDao;
|
||
use app\model\order\OrderCenter;
|
||
use crmeb\services\invoice\Invoice;
|
||
use app\dao\order\OrderInvoiceDao;
|
||
use app\services\user\UserInvoiceServices;
|
||
use app\services\user\UserServices;
|
||
use crmeb\trade\order\type\OrderTypeRegistry;
|
||
use think\facade\Log;
|
||
|
||
|
||
/**
|
||
* Class OrderInvoiceServices
|
||
* @package app\services\order
|
||
*/
|
||
class OrderInvoiceServices extends BaseService
|
||
{
|
||
/**
|
||
* @var OrderInvoiceDao
|
||
*/
|
||
protected $dao;
|
||
|
||
/**
|
||
* OrderInvoiceServices constructor.
|
||
* @param OrderInvoiceDao $dao
|
||
*/
|
||
public function __construct(OrderInvoiceDao $dao)
|
||
{
|
||
$this->dao = $dao;
|
||
}
|
||
|
||
public function chart(array $where)
|
||
{
|
||
$where['is_pay'] = 1;
|
||
$where['is_del'] = 0;
|
||
//全部
|
||
$data['all'] = (string)$this->dao->count($where);
|
||
//待开
|
||
$where['type'] = 1;
|
||
$data['noOpened'] = (string)$this->dao->count($where);
|
||
//已开
|
||
$where['type'] = 2;
|
||
$data['opened'] = (string)$this->dao->count($where);
|
||
//退款
|
||
$where['type'] = 3;
|
||
$data['refund'] = (string)$this->dao->count($where);
|
||
$data['elec_invoice'] = (int)sys_config('elec_invoice', 0);
|
||
return $data;
|
||
}
|
||
|
||
/**
|
||
* 后台获取开票列表
|
||
* @param array $where
|
||
* @return array
|
||
* @throws \think\db\exception\DataNotFoundException
|
||
* @throws \think\db\exception\DbException
|
||
* @throws \think\db\exception\ModelNotFoundException
|
||
*/
|
||
public function getList(array $where)
|
||
{
|
||
[$page, $list] = $this->getPageValue();
|
||
$field = 'id as invoice_id,order_id,header_type,type,name,duty_number,drawer_phone,email,tell,address,bank,card_number,is_invoice,invoice_number,remark as invoice_reamrk,invoice_time,add_time as invoice_add_time,unique_num,invoice_num,invoice_type,invoice_serial_number,red_invoice_num';
|
||
$where['is_pay'] = 1;
|
||
$where['is_del'] = 0;
|
||
// 订单中台关联查询:带 source_type/source_id,便于实时推导待发货/待收货
|
||
$list = $this->dao->getList($where, $field, ['order' => function ($query) {
|
||
$query->field('id,order_no,uid,pay_price,status_code,refund_status,extra_info,add_time,source_type,source_id');
|
||
}], 'add_time desc', $page, $list);
|
||
// 按源订单类型批量解析业务子状态(extra_info 快照会被 syncStatus 覆盖成 processing,不能直接用)
|
||
$businessStatusMap = $this->resolveBusinessStatusMap($list);
|
||
// 批量取下单用户,作为收货人姓名电话的兜底数据
|
||
$uids = array_filter(array_unique(array_column(array_column($list, 'order'), 'uid')));
|
||
$userList = $uids ? app()->make(UserServices::class)->getColumn([['uid', 'in', $uids]], 'uid,real_name,nickname,phone', 'uid') : [];
|
||
foreach ($list as &$item) {
|
||
$order = $item['order'] ?? [];
|
||
$extra = $order['extra_info'] ?? '';
|
||
$extra = is_string($extra) ? (json_decode($extra, true) ?: []) : ($extra ?: []);
|
||
$contact = $extra['contact'] ?? [];
|
||
$user = $userList[$order['uid']] ?? [];
|
||
$item['id'] = $order['id'] ?? 0;
|
||
// 前端契约:列表展示的订单号对应中台 order_no
|
||
$item['order_id'] = $order['order_no'] ?? '';
|
||
$item['pay_price'] = $order['pay_price'] ?? 0.00;
|
||
// 收货人姓名电话优先取订单快照,空串需回退下单用户资料(?? 只拦 null,拦不住空串)
|
||
$contactName = trim((string)($contact['name'] ?? ''));
|
||
$contactPhone = trim((string)($contact['phone'] ?? ''));
|
||
$item['real_name'] = $contactName !== '' ? $contactName : trim((string)($user['real_name'] ?? ($user['nickname'] ?? '')));
|
||
$item['user_phone'] = $contactPhone !== '' ? $contactPhone : trim((string)($user['phone'] ?? ''));
|
||
$statusCode = (string)($order['status_code'] ?? '');
|
||
$item['status_code'] = $statusCode;
|
||
$centerId = (int)($order['id'] ?? 0);
|
||
$businessStatus = $businessStatusMap[$centerId] ?? null;
|
||
if (is_array($businessStatus)) {
|
||
if (empty($businessStatus['label']) && !empty($businessStatus['text'])) {
|
||
$businessStatus['label'] = $businessStatus['text'];
|
||
}
|
||
$item['business_status'] = $businessStatus;
|
||
// 列表唯一展示文案:优先业务子状态(待收货),否则中台统一状态
|
||
$item['status_text'] = (string)($businessStatus['label'] ?: ($businessStatus['text'] ?? ''));
|
||
} else {
|
||
$item['business_status'] = null;
|
||
$item['status_text'] = '';
|
||
}
|
||
if ($item['status_text'] === '') {
|
||
// 管理端与订单中台列表一致:processing 展示「待处理」
|
||
$item['status_text'] = $statusCode === OrderCenter::STATUS_PROCESSING
|
||
? '待处理'
|
||
: (OrderCenter::$statusMap[$statusCode] ?? ($statusCode !== '' ? $statusCode : '--'));
|
||
}
|
||
// 兼容旧前端/移动端数字状态
|
||
$item['status'] = $this->statusToLegacy($statusCode);
|
||
$item['refund_status'] = $order['refund_status'] ?? 0;
|
||
$item['add_time'] = date('Y-m-d H:i:s', $order['add_time'] ?? $item['invoice_add_time'] ?? time());
|
||
$item['invoice_add_time'] = date('Y-m-d H:i:s', $item['invoice_add_time']);
|
||
// 中台关联原始数据不再下发给前端
|
||
unset($item['order']);
|
||
}
|
||
unset($item);
|
||
$count = $this->dao->count($where);
|
||
return compact('list', 'count');
|
||
}
|
||
|
||
/**
|
||
* 批量从源订单适配器推导业务子状态
|
||
* @param array $list 含 order 关联的发票列表
|
||
* @return array<int, array|null> order_center.id => business_status
|
||
*/
|
||
protected function resolveBusinessStatusMap(array $list): array
|
||
{
|
||
$grouped = [];
|
||
$centerIndex = [];
|
||
foreach ($list as $row) {
|
||
$order = $row['order'] ?? [];
|
||
$centerId = (int)($order['id'] ?? 0);
|
||
$sourceType = (string)($order['source_type'] ?? '');
|
||
$sourceId = (int)($order['source_id'] ?? 0);
|
||
if ($centerId <= 0 || $sourceType === '' || $sourceId <= 0) {
|
||
continue;
|
||
}
|
||
$grouped[$sourceType][$sourceId] = true;
|
||
$centerIndex[$sourceType][$sourceId][] = $centerId;
|
||
}
|
||
$result = [];
|
||
foreach ($grouped as $sourceType => $idMap) {
|
||
try {
|
||
$adapter = OrderTypeRegistry::getAdapter($sourceType);
|
||
$modelClass = $adapter::getModelClass();
|
||
/** @var \crmeb\basic\BaseModel $model */
|
||
$model = app()->make($modelClass);
|
||
$sourceRows = $model->whereIn('id', array_keys($idMap))->select()->toArray();
|
||
foreach ($sourceRows as $sourceOrder) {
|
||
$sourceId = (int)($sourceOrder['id'] ?? 0);
|
||
$businessStatus = $adapter->resolveBusinessStatus($sourceOrder);
|
||
foreach ($centerIndex[$sourceType][$sourceId] ?? [] as $centerId) {
|
||
$result[$centerId] = $businessStatus;
|
||
}
|
||
}
|
||
} catch (\Throwable $e) {
|
||
Log::warning('发票列表推导订单业务状态失败: ' . $e->getMessage(), ['source_type' => $sourceType]);
|
||
}
|
||
}
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* 订单中台状态码映射为旧版订单状态数值(兼容移动端发票列表 _type)
|
||
* @param string $statusCode
|
||
* @return int
|
||
*/
|
||
public function statusToLegacy(string $statusCode): int
|
||
{
|
||
return match ($statusCode) {
|
||
OrderCenter::STATUS_PENDING_PAYMENT => 0,
|
||
OrderCenter::STATUS_PROCESSING => 0,
|
||
OrderCenter::STATUS_PENDING_REVIEW => 2,
|
||
OrderCenter::STATUS_COMPLETED => 3,
|
||
OrderCenter::STATUS_CANCELED => -1,
|
||
OrderCenter::STATUS_AFTER_SALE, OrderCenter::STATUS_REFUNDING => -3,
|
||
OrderCenter::STATUS_REFUNDED => -2,
|
||
OrderCenter::STATUS_DELETED => -4,
|
||
default => 0,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* 前端获取开票列表(带商品信息)
|
||
* @param $where
|
||
* @return array
|
||
*/
|
||
public function getOrderInvoiceList(array $where)
|
||
{
|
||
[$page, $list] = $this->getPageValue();
|
||
$where['is_pay'] = 1;
|
||
$where['is_del'] = 0;
|
||
$list = $this->dao->getList($where, '*', ['order' => function ($query) {
|
||
$query->field('id,order_no,pay_price,extra_info');
|
||
}], 'add_time desc', $page, $list);
|
||
foreach ($list as &$item) {
|
||
$order = $item['order'] ?? [];
|
||
$extra = $order['extra_info'] ?? '';
|
||
$extra = is_string($extra) ? (json_decode($extra, true) ?: []) : ($extra ?: []);
|
||
$order['cartInfo'] = $this->buildLegacyCartInfo($extra);
|
||
$order['pay_price'] = $order['pay_price'] ?? '0.00';
|
||
// 移动端跳转开票详情携带的订单号对应中台 order_no
|
||
$order['order_id'] = $order['order_no'] ?? '';
|
||
unset($order['extra_info']);
|
||
$item['order'] = $order;
|
||
}
|
||
unset($item);
|
||
return $list;
|
||
}
|
||
|
||
/**
|
||
* 由订单中台商品快照合成旧版 cartInfo 结构(兼容移动端旧组件)
|
||
*
|
||
* 移动端 orderGoods 组件按旧版结构取值:attrInfo 存在时图片/规格价取
|
||
* attrInfo.image / attrInfo.price,商品单价取 productInfo.price,
|
||
* 三者缺一会导致商品图片或金额不回显。
|
||
*
|
||
* @param array $extra 解析后的 extra_info
|
||
* @return array
|
||
*/
|
||
public function buildLegacyCartInfo(array $extra): array
|
||
{
|
||
$cartInfo = [];
|
||
foreach ($extra['items'] ?? [] as $product) {
|
||
$attrInfo = is_array($product['attr_info'] ?? null) ? $product['attr_info'] : [];
|
||
// 规格图优先,缺省回退商品主图;规格价优先,缺省回退商品价
|
||
$image = $attrInfo['image'] ?? ($product['image'] ?? '');
|
||
$price = $attrInfo['price'] ?? ($product['price'] ?? 0);
|
||
$cartInfo[] = [
|
||
'cart_num' => $product['cart_num'] ?? 1,
|
||
'truePrice' => $product['true_price'] ?? 0,
|
||
// 开票仅针对已支付订单,商品按有效态展示(is_valid 缺省会被组件置灰半透明)
|
||
'is_valid' => true,
|
||
'productInfo' => [
|
||
'image' => $product['image'] ?? '',
|
||
'store_name' => $product['store_name'] ?? '',
|
||
'price' => $price,
|
||
'attrInfo' => array_merge($attrInfo, [
|
||
'suk' => $attrInfo['suk'] ?? ($product['sku'] ?? ''),
|
||
'image' => $image,
|
||
'price' => $price,
|
||
]),
|
||
],
|
||
];
|
||
}
|
||
return $cartInfo;
|
||
}
|
||
|
||
/**
|
||
* 订单申请开票
|
||
* @param int $uid
|
||
* @param $order_id
|
||
* @param int $invoice_id
|
||
* @return mixed
|
||
* @throws \think\db\exception\DataNotFoundException
|
||
* @throws \think\db\exception\DbException
|
||
* @throws \think\db\exception\ModelNotFoundException
|
||
*/
|
||
public function makeUp(int $uid, $order_id, int $invoice_id)
|
||
{
|
||
if (!$order_id) throw new AdminException('参数错误');
|
||
if (!$invoice_id) throw new AdminException('请选择发票');
|
||
|
||
/** @var OrderCenterDao $orderCenterServices */
|
||
$orderCenterServices = app()->make(OrderCenterDao::class);
|
||
/** @var UserInvoiceServices $userInvoiceServices */
|
||
$userInvoiceServices = app()->make(UserInvoiceServices::class);
|
||
// 订单中台以 order_no 为全局单号,兼容传订单号或中台ID
|
||
$order = $orderCenterServices->getOne(['order_no|id' => $order_id, 'is_del' => 0]);
|
||
if (!$order) {
|
||
throw new AdminException('订单不存在');
|
||
}
|
||
// 校验订单归属,防止为他人订单申请开票
|
||
if ((int)$order['uid'] !== $uid) {
|
||
throw new AdminException('订单不存在');
|
||
}
|
||
//检测再带查询
|
||
$invoice = $userInvoiceServices->checkInvoice($invoice_id, $uid);
|
||
|
||
if ($this->dao->getOne(['order_id' => $order['id'], 'uid' => $uid])) {
|
||
throw new AdminException('发票已申请');
|
||
}
|
||
if ($order['refund_status'] == 2) {
|
||
throw new AdminException('订单已退款');
|
||
}
|
||
if ($order['refund_status'] == 1) {
|
||
throw new AdminException('正在申请退款中');
|
||
}
|
||
// 仅已支付订单可申请开票,未支付订单的开票记录不在列表中展示
|
||
if ($order['pay_status'] == 0) {
|
||
throw new AdminException('订单未支付,无法申请开票');
|
||
}
|
||
// 显式构建申请数据,避免抬头表多余字段(is_default 等)写入开票表
|
||
$data = [];
|
||
$data['uid'] = $uid;
|
||
$data['category'] = 'order';
|
||
$data['order_id'] = (int)$order['id'];
|
||
$data['invoice_id'] = $invoice_id;
|
||
$data['header_type'] = $invoice['header_type'];
|
||
$data['type'] = $invoice['type'];
|
||
$data['name'] = $invoice['name'];
|
||
$data['duty_number'] = $invoice['duty_number'];
|
||
$data['drawer_phone'] = $invoice['drawer_phone'];
|
||
$data['email'] = $invoice['email'];
|
||
$data['tell'] = $invoice['tell'];
|
||
$data['address'] = $invoice['address'];
|
||
$data['bank'] = $invoice['bank'];
|
||
$data['card_number'] = $invoice['card_number'];
|
||
$data['is_pay'] = $order['pay_status'] == 1 ? 1 : 0;
|
||
$data['add_time'] = time();
|
||
if (!$re = $this->dao->save($data)) {
|
||
throw new AdminException('申请失败');
|
||
}
|
||
if (sys_config('elec_invoice', 1) == 1 && sys_config('auto_invoice', 1) == 1 && $data['is_pay'] == 1) {
|
||
//自动开票
|
||
OrderInvoiceJob::dispatchSecs(10, 'autoInvoice', [$re->id]);
|
||
}
|
||
|
||
return ['id' => $re->id];
|
||
}
|
||
|
||
/**
|
||
* 下单勾选发票时创建开票申请记录
|
||
* 列表/支付回写以 eb_order_invoice 为准;中台 order_center.invoice 只作快照展示
|
||
* @param int $uid 用户uid
|
||
* @param int $orderCenterId 订单中台主键
|
||
* @param array $invoice 用户发票抬头(eb_user_invoice)
|
||
* @param int $isPay 是否已支付 0/1
|
||
* @return int|null 开票记录id
|
||
*/
|
||
public function createFromOrder(int $uid, int $orderCenterId, array $invoice, int $isPay = 0): ?int
|
||
{
|
||
if (!$orderCenterId || empty($invoice)) {
|
||
return null;
|
||
}
|
||
// 同一订单已有申请则跳过,避免重复写入
|
||
if ($this->dao->getOne(['order_id' => $orderCenterId, 'is_del' => 0])) {
|
||
return null;
|
||
}
|
||
$data = [];
|
||
$data['uid'] = $uid;
|
||
$data['category'] = 'order';
|
||
$data['order_id'] = $orderCenterId;
|
||
$data['invoice_id'] = (int)($invoice['id'] ?? 0);
|
||
$data['header_type'] = $invoice['header_type'] ?? 1;
|
||
$data['type'] = $invoice['type'] ?? 1;
|
||
$data['name'] = $invoice['name'] ?? '';
|
||
$data['duty_number'] = $invoice['duty_number'] ?? '';
|
||
$data['drawer_phone'] = $invoice['drawer_phone'] ?? '';
|
||
$data['email'] = $invoice['email'] ?? '';
|
||
$data['tell'] = $invoice['tell'] ?? '';
|
||
$data['address'] = $invoice['address'] ?? '';
|
||
$data['bank'] = $invoice['bank'] ?? '';
|
||
$data['card_number'] = $invoice['card_number'] ?? '';
|
||
$data['is_pay'] = $isPay;
|
||
$data['add_time'] = time();
|
||
if (!$re = $this->dao->save($data)) {
|
||
return null;
|
||
}
|
||
return (int)$re->id;
|
||
}
|
||
|
||
public function setInvoice(int $id, array $data)
|
||
{
|
||
$orderInvoice = $this->dao->get($id);
|
||
if (!$orderInvoice) {
|
||
throw new AdminException('数据不存在');
|
||
}
|
||
if ($data['is_invoice'] == 1) {
|
||
$data['invoice_time'] = time();
|
||
}
|
||
if (!$this->dao->update($id, $data, 'id')) {
|
||
throw new AdminException('设置失败');
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 开具发票
|
||
* @param $id
|
||
* @return bool
|
||
* @throws \ReflectionException
|
||
* @throws \think\db\exception\DataNotFoundException
|
||
* @throws \think\db\exception\DbException
|
||
* @throws \think\db\exception\ModelNotFoundException
|
||
* @author wuhaotian
|
||
* @email 442384644@qq.com
|
||
* @date 2024/5/15
|
||
*/
|
||
public function invoiceIssuance($id)
|
||
{
|
||
if (sys_config('elec_invoice', 1) != 1) {
|
||
throw new AdminException('电子发票功能未开启,请在一号通中开启并且在商城后台一号通配置中开启');
|
||
}
|
||
$info = $this->dao->getOne(['id' => $id]);
|
||
if (!$info) {
|
||
throw new AdminException('发票不存在');
|
||
}
|
||
/** @var OrderCenterDao $orderDao */
|
||
$orderDao = app()->make(OrderCenterDao::class);
|
||
$orderInfo = $orderDao->get($info['order_id']);
|
||
if (!$orderInfo) {
|
||
throw new AdminException('订单不存在');
|
||
}
|
||
// 从订单中台 extra_info.items 读取商品快照(旧 eb_store_order_cart_info 已废弃)
|
||
$extra = $orderInfo['extra_info'] ?? null;
|
||
$extra = is_string($extra) ? json_decode($extra, true) : ($extra ?? []);
|
||
$cartInfo = $extra['items'] ?? [];
|
||
$data = [];
|
||
$data['is_tax_inclusive'] = 1;
|
||
$data['account_name'] = $info['name'];
|
||
$data['bank_name'] = '';
|
||
$data['bank_account'] = '';
|
||
$data['telephone'] = $info['drawer_phone'];
|
||
$data['company_address'] = '';
|
||
$data['drawer'] = '';
|
||
$data['amount'] = $orderInfo['pay_price'];
|
||
if ($info['header_type'] == 1) {
|
||
$data['invoice_type'] = 82;
|
||
$data['tax_id'] = '';
|
||
$data['is_enterprise'] = 0;
|
||
} else {
|
||
$data['invoice_type'] = $info['type'] == 1 ? 82 : 81;
|
||
$data['tax_id'] = $info['duty_number'];
|
||
$data['is_enterprise'] = 1;
|
||
}
|
||
$goods = [];
|
||
foreach ($cartInfo as $item) {
|
||
$goods[] = [
|
||
'cate_id' => sys_config('elec_invoice_cate', 1799),
|
||
'store_name' => $item['store_name'] ?? '',
|
||
'unit_price' => bcadd((string)($item['true_price'] ?? '0.00'), (string)($item['postage_price'] ?? '0.00'), 2),
|
||
'num' => $item['cart_num'] ?? 1,
|
||
'tax_rate' => sys_config('elec_invoice_tax_rate', 13),
|
||
];
|
||
}
|
||
$data['goods'] = $goods;
|
||
// 一号通开票的唯一标识使用中台订单号 order_no
|
||
try {
|
||
$invoice = app()->make(Invoice::class, [yihaotong_config()]);
|
||
$res = $invoice->invoiceIssuance($orderInfo['order_no'], $data);
|
||
if ($res['status'] == 200) {
|
||
$this->dao->update($id, [
|
||
'is_invoice' => 1,
|
||
'unique_num' => $orderInfo['order_no'],
|
||
'invoice_num' => $res['data']['invoice_num'],
|
||
'invoice_type' => $res['data']['invoice_type'],
|
||
'invoice_serial_number' => $res['data']['invoice_serial_number'],
|
||
'invoice_time' => time()
|
||
]);
|
||
}
|
||
} catch (\Exception $e) {
|
||
Log::error('自动开具发票失败,失败原因:' . $e->getMessage());
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 未开发票自动开具电子发票
|
||
* @return bool
|
||
* @author wuhaotian
|
||
* @email 442384644@qq.com
|
||
* @date 2024/5/15
|
||
*/
|
||
public function autoInvoice()
|
||
{
|
||
if (sys_config('elec_invoice', 1) != 1) {
|
||
return true;
|
||
}
|
||
if (sys_config('auto_invoice', 1) == 1) {
|
||
$list = $this->dao->getColumn([
|
||
['is_pay', '=', 1],
|
||
['is_invoice', '=', 1],
|
||
['unique_num', '=', ''],
|
||
['is_del', '=', 0],
|
||
['add_time', '<', time() - 60],
|
||
], 'id');
|
||
if ($list) {
|
||
foreach ($list as $item) {
|
||
//自动开票
|
||
OrderInvoiceJob::dispatchSecs(10, 'autoInvoice', [$item]);
|
||
}
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 退款订单自动冲红
|
||
* @return bool
|
||
* @author wuhaotian
|
||
* @email 442384644@qq.com
|
||
* @date 2024/5/15
|
||
*/
|
||
public function autoInvoiceRed()
|
||
{
|
||
if (sys_config('elec_invoice', 1) != 1) {
|
||
return true;
|
||
}
|
||
if (sys_config('auto_invoice', 1) == 1) {
|
||
$list = $this->dao->getColumn([
|
||
['is_pay', '=', 1],
|
||
['is_invoice', '=', 1],
|
||
['unique_num', '<>', ''],
|
||
['red_invoice_num', '=', ''],
|
||
['is_del', '=', 1],
|
||
['add_time', '<', time() - 60],
|
||
], 'id');
|
||
if ($list) {
|
||
foreach ($list as $item) {
|
||
//自动冲红
|
||
OrderInvoiceJob::dispatchSecs(10, 'autoInvoiceRed', [$item]);
|
||
}
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* 负数发票开具
|
||
* @param $id
|
||
* @return bool
|
||
* @throws \think\db\exception\DataNotFoundException
|
||
* @throws \think\db\exception\DbException
|
||
* @throws \think\db\exception\ModelNotFoundException
|
||
* @author wuhaotian
|
||
* @email 442384644@qq.com
|
||
* @date 2024/5/16
|
||
*/
|
||
public function redInvoiceIssuance($id)
|
||
{
|
||
$invoiceInfo = $this->dao->get($id);
|
||
if (!$invoiceInfo) throw new AdminException('发票不存在');
|
||
if ($invoiceInfo['is_pay'] == 0 || $invoiceInfo['is_invoice'] == 0 || $invoiceInfo['unique_num'] === '') {
|
||
throw new AdminException('发票状态有误,请检查');
|
||
}
|
||
$invoice = app()->make(Invoice::class, [yihaotong_config()]);
|
||
// 驱动契约要求 camelCase 键名 invoiceNum
|
||
$res = $invoice->redInvoiceIssuance(['invoiceNum' => $invoiceInfo['invoice_num'], 'apply_type' => '01']);
|
||
if ($res['status'] != 200) throw new AdminException('开具负数发票失败,请检查');
|
||
$this->dao->update($id, ['red_invoice_num' => 1]);
|
||
return true;
|
||
}
|
||
}
|