394 lines
14 KiB
PHP
394 lines
14 KiB
PHP
<?php
|
|
|
|
namespace app\controller\admin;
|
|
|
|
use app\support\EnterpriseCustomerService;
|
|
use app\support\EnterpriseOrderService;
|
|
use app\support\EnterpriseWebhookService;
|
|
use support\Request;
|
|
use support\think\Db;
|
|
|
|
class CustomersController
|
|
{
|
|
public function index(Request $request)
|
|
{
|
|
$keyword = trim((string)$request->input('keyword', ''));
|
|
$status = trim((string)$request->input('status', ''));
|
|
|
|
$query = Db::name('enterprise_customers')->order('id', 'desc');
|
|
if ($keyword !== '') {
|
|
$query->where(function ($builder) use ($keyword) {
|
|
$builder->whereRaw(
|
|
'(customer_code LIKE :keyword_code OR customer_name LIKE :keyword_name OR contact_name LIKE :keyword_contact OR contact_mobile LIKE :keyword_mobile)',
|
|
[
|
|
'keyword_code' => "%{$keyword}%",
|
|
'keyword_name' => "%{$keyword}%",
|
|
'keyword_contact' => "%{$keyword}%",
|
|
'keyword_mobile' => "%{$keyword}%",
|
|
]
|
|
);
|
|
});
|
|
}
|
|
if (in_array($status, ['enabled', 'disabled'], true)) {
|
|
$query->where('status', $status);
|
|
}
|
|
|
|
$rows = $query->select()->toArray();
|
|
$customerIds = array_map(static fn(array $item) => (int)$item['id'], $rows);
|
|
$appCountMap = $this->countMap('enterprise_customer_apps', $customerIds);
|
|
$orderCountMap = $this->countMap('enterprise_customer_order_refs', $customerIds);
|
|
$eventCountMap = $this->countMap('enterprise_order_events', $customerIds);
|
|
|
|
return api_success([
|
|
'list' => array_map(function (array $item) use ($appCountMap, $orderCountMap, $eventCountMap) {
|
|
$customer = $this->customerService()->formatCustomer($item);
|
|
$id = (int)$customer['id'];
|
|
$customer['app_count'] = (int)($appCountMap[$id] ?? 0);
|
|
$customer['order_count'] = (int)($orderCountMap[$id] ?? 0);
|
|
$customer['event_count'] = (int)($eventCountMap[$id] ?? 0);
|
|
return $customer;
|
|
}, $rows),
|
|
]);
|
|
}
|
|
|
|
public function detail(Request $request)
|
|
{
|
|
$id = (int)$request->input('id', 0);
|
|
if ($id <= 0) {
|
|
return api_error('客户 ID 不能为空', 422);
|
|
}
|
|
|
|
$customer = Db::name('enterprise_customers')->where('id', $id)->find();
|
|
if (!$customer) {
|
|
return api_error('客户不存在', 404);
|
|
}
|
|
|
|
$apps = Db::name('enterprise_customer_apps')
|
|
->where('customer_id', $id)
|
|
->order('id', 'desc')
|
|
->select()
|
|
->toArray();
|
|
|
|
return api_success([
|
|
'customer' => $this->customerService()->formatCustomer($customer),
|
|
'apps' => array_map(fn(array $item) => $this->customerService()->formatApp($item), $apps),
|
|
]);
|
|
}
|
|
|
|
public function save(Request $request)
|
|
{
|
|
$id = (int)$request->input('id', 0);
|
|
$customerName = trim((string)$request->input('customer_name', ''));
|
|
if ($customerName === '') {
|
|
return api_error('客户名称不能为空', 422);
|
|
}
|
|
|
|
$status = trim((string)$request->input('status', 'enabled'));
|
|
if (!in_array($status, ['enabled', 'disabled'], true)) {
|
|
return api_error('客户状态不正确', 422);
|
|
}
|
|
|
|
$webhookUrl = trim((string)$request->input('webhook_url', ''));
|
|
if ($webhookUrl !== '' && !preg_match('/^https?:\/\//i', $webhookUrl)) {
|
|
return api_error('Webhook URL 必须以 http 或 https 开头', 422);
|
|
}
|
|
|
|
$now = date('Y-m-d H:i:s');
|
|
$payload = [
|
|
'customer_name' => $customerName,
|
|
'contact_name' => trim((string)$request->input('contact_name', '')),
|
|
'contact_mobile' => trim((string)$request->input('contact_mobile', '')),
|
|
'contact_email' => trim((string)$request->input('contact_email', '')),
|
|
'settlement_type' => 'monthly',
|
|
'webhook_url' => $webhookUrl,
|
|
'webhook_enabled' => $request->input('webhook_enabled', false) ? 1 : 0,
|
|
'status' => $status,
|
|
'remark' => trim((string)$request->input('remark', '')),
|
|
'updated_at' => $now,
|
|
];
|
|
|
|
Db::startTrans();
|
|
try {
|
|
if ($id > 0) {
|
|
$customer = Db::name('enterprise_customers')->where('id', $id)->find();
|
|
if (!$customer) {
|
|
Db::rollback();
|
|
return api_error('客户不存在', 404);
|
|
}
|
|
Db::name('enterprise_customers')->where('id', $id)->update($payload);
|
|
} else {
|
|
$payload['customer_code'] = $this->customerService()->generateCustomerCode();
|
|
$payload['created_at'] = $now;
|
|
$id = (int)Db::name('enterprise_customers')->insertGetId($payload);
|
|
}
|
|
|
|
$customer = Db::name('enterprise_customers')->where('id', $id)->find();
|
|
if ($customer) {
|
|
$this->customerService()->ensureVirtualUser($customer);
|
|
}
|
|
Db::commit();
|
|
} catch (\Throwable $e) {
|
|
Db::rollback();
|
|
return api_error('客户保存失败', 500, [
|
|
'detail' => $e->getMessage(),
|
|
]);
|
|
}
|
|
|
|
return api_success([
|
|
'id' => $id,
|
|
], $request->input('id', 0) ? '客户已更新' : '客户已创建');
|
|
}
|
|
|
|
public function createApp(Request $request)
|
|
{
|
|
$customerId = (int)$request->input('customer_id', 0);
|
|
$appName = trim((string)$request->input('app_name', '默认应用'));
|
|
if ($customerId <= 0) {
|
|
return api_error('客户 ID 不能为空', 422);
|
|
}
|
|
if ($appName === '') {
|
|
$appName = '默认应用';
|
|
}
|
|
|
|
$customer = Db::name('enterprise_customers')->where('id', $customerId)->find();
|
|
if (!$customer) {
|
|
return api_error('客户不存在', 404);
|
|
}
|
|
|
|
$secret = $this->customerService()->generateAppSecret();
|
|
$now = date('Y-m-d H:i:s');
|
|
$appId = (int)Db::name('enterprise_customer_apps')->insertGetId([
|
|
'customer_id' => $customerId,
|
|
'app_name' => $appName,
|
|
'app_key' => $this->customerService()->generateAppKey(),
|
|
'app_secret_cipher' => $this->customerService()->encryptSecret($secret),
|
|
'secret_last4' => substr($secret, -4),
|
|
'status' => 'enabled',
|
|
'last_used_at' => null,
|
|
'created_at' => $now,
|
|
'updated_at' => $now,
|
|
]);
|
|
|
|
$app = Db::name('enterprise_customer_apps')->where('id', $appId)->find();
|
|
return api_success([
|
|
'app' => $this->customerService()->formatApp($app),
|
|
'app_secret' => $secret,
|
|
], '应用 Key 已创建,请立即复制保存 Secret');
|
|
}
|
|
|
|
public function updateAppStatus(Request $request)
|
|
{
|
|
$id = (int)$request->input('id', 0);
|
|
$status = trim((string)$request->input('status', ''));
|
|
if ($id <= 0 || !in_array($status, ['enabled', 'disabled'], true)) {
|
|
return api_error('应用 ID 或状态不正确', 422);
|
|
}
|
|
|
|
$app = Db::name('enterprise_customer_apps')->where('id', $id)->find();
|
|
if (!$app) {
|
|
return api_error('应用不存在', 404);
|
|
}
|
|
|
|
Db::name('enterprise_customer_apps')->where('id', $id)->update([
|
|
'status' => $status,
|
|
'updated_at' => date('Y-m-d H:i:s'),
|
|
]);
|
|
|
|
return api_success([
|
|
'id' => $id,
|
|
'status' => $status,
|
|
], $status === 'enabled' ? '应用已启用' : '应用已停用');
|
|
}
|
|
|
|
public function resetAppSecret(Request $request)
|
|
{
|
|
$id = (int)$request->input('id', 0);
|
|
if ($id <= 0) {
|
|
return api_error('应用 ID 不能为空', 422);
|
|
}
|
|
|
|
$app = Db::name('enterprise_customer_apps')->where('id', $id)->find();
|
|
if (!$app) {
|
|
return api_error('应用不存在', 404);
|
|
}
|
|
|
|
$secret = $this->customerService()->generateAppSecret();
|
|
Db::name('enterprise_customer_apps')->where('id', $id)->update([
|
|
'app_secret_cipher' => $this->customerService()->encryptSecret($secret),
|
|
'secret_last4' => substr($secret, -4),
|
|
'updated_at' => date('Y-m-d H:i:s'),
|
|
]);
|
|
|
|
$fresh = Db::name('enterprise_customer_apps')->where('id', $id)->find();
|
|
return api_success([
|
|
'app' => $this->customerService()->formatApp($fresh),
|
|
'app_secret' => $secret,
|
|
], '应用 Secret 已重置,请立即复制保存');
|
|
}
|
|
|
|
public function orders(Request $request)
|
|
{
|
|
$customerId = (int)$request->input('customer_id', 0);
|
|
if ($customerId <= 0) {
|
|
return api_error('客户 ID 不能为空', 422);
|
|
}
|
|
|
|
$rows = Db::name('enterprise_customer_order_refs')
|
|
->alias('r')
|
|
->leftJoin('orders o', 'o.id = r.order_id')
|
|
->leftJoin('order_products p', 'p.order_id = r.order_id')
|
|
->field([
|
|
'r.id',
|
|
'r.customer_id',
|
|
'r.external_order_no',
|
|
'r.order_id',
|
|
'r.order_no',
|
|
'r.appraisal_no',
|
|
'r.created_at',
|
|
'o.order_status',
|
|
'o.display_status',
|
|
'o.pay_amount',
|
|
'p.product_name',
|
|
])
|
|
->where('r.customer_id', $customerId)
|
|
->order('r.id', 'desc')
|
|
->select()
|
|
->toArray();
|
|
|
|
return api_success([
|
|
'list' => array_map(static fn(array $item) => [
|
|
'id' => (int)$item['id'],
|
|
'customer_id' => (int)$item['customer_id'],
|
|
'external_order_no' => (string)$item['external_order_no'],
|
|
'order_id' => (int)$item['order_id'],
|
|
'order_no' => (string)$item['order_no'],
|
|
'appraisal_no' => (string)$item['appraisal_no'],
|
|
'product_name' => (string)($item['product_name'] ?: '待完善物品信息'),
|
|
'order_status' => (string)($item['order_status'] ?? ''),
|
|
'display_status' => (string)($item['display_status'] ?? ''),
|
|
'pay_amount' => (float)($item['pay_amount'] ?? 0),
|
|
'created_at' => (string)$item['created_at'],
|
|
], $rows),
|
|
]);
|
|
}
|
|
|
|
public function events(Request $request)
|
|
{
|
|
$customerId = (int)$request->input('customer_id', 0);
|
|
if ($customerId <= 0) {
|
|
return api_error('客户 ID 不能为空', 422);
|
|
}
|
|
|
|
$rows = Db::name('enterprise_order_events')
|
|
->where('customer_id', $customerId)
|
|
->order('id', 'desc')
|
|
->limit(200)
|
|
->select()
|
|
->toArray();
|
|
|
|
return api_success([
|
|
'list' => array_map(fn(array $item) => $this->webhookService()->formatEvent($item), $rows),
|
|
]);
|
|
}
|
|
|
|
public function deliveries(Request $request)
|
|
{
|
|
$customerId = (int)$request->input('customer_id', 0);
|
|
$eventId = (int)$request->input('event_id', 0);
|
|
if ($customerId <= 0 && $eventId <= 0) {
|
|
return api_error('客户 ID 或事件 ID 不能为空', 422);
|
|
}
|
|
|
|
$query = Db::name('enterprise_webhook_deliveries')->order('id', 'desc')->limit(200);
|
|
if ($customerId > 0) {
|
|
$query->where('customer_id', $customerId);
|
|
}
|
|
if ($eventId > 0) {
|
|
$query->where('event_id', $eventId);
|
|
}
|
|
|
|
$rows = $query->select()->toArray();
|
|
|
|
return api_success([
|
|
'list' => array_map(fn(array $item) => $this->webhookService()->formatDelivery($item), $rows),
|
|
]);
|
|
}
|
|
|
|
public function resendEvent(Request $request)
|
|
{
|
|
$eventId = (int)$request->input('event_id', 0);
|
|
if ($eventId <= 0) {
|
|
return api_error('事件 ID 不能为空', 422);
|
|
}
|
|
|
|
try {
|
|
$result = $this->webhookService()->deliverEvent($eventId, true);
|
|
} catch (\RuntimeException $e) {
|
|
return api_error($e->getMessage(), 422);
|
|
} catch (\Throwable $e) {
|
|
return api_error('事件补发失败', 500, [
|
|
'detail' => $e->getMessage(),
|
|
]);
|
|
}
|
|
|
|
return api_success([
|
|
'delivery' => $this->webhookService()->formatDelivery($result['delivery']),
|
|
'sent' => (bool)$result['sent'],
|
|
], $result['sent'] ? '事件已补发成功' : '事件补发未成功,请查看推送记录');
|
|
}
|
|
|
|
public function orderProgress(Request $request)
|
|
{
|
|
$customerId = (int)$request->input('customer_id', 0);
|
|
$externalOrderNo = trim((string)$request->input('external_order_no', ''));
|
|
if ($customerId <= 0 || $externalOrderNo === '') {
|
|
return api_error('客户 ID 和外部订单号不能为空', 422);
|
|
}
|
|
|
|
$customer = Db::name('enterprise_customers')->where('id', $customerId)->find();
|
|
if (!$customer) {
|
|
return api_error('客户不存在', 404);
|
|
}
|
|
|
|
try {
|
|
$order = (new EnterpriseOrderService())->findOrder($customer, $externalOrderNo, '');
|
|
} catch (\Throwable $e) {
|
|
return api_error($e->getMessage(), 404);
|
|
}
|
|
|
|
return api_success([
|
|
'order' => $order,
|
|
]);
|
|
}
|
|
|
|
private function countMap(string $table, array $customerIds): array
|
|
{
|
|
if (!$customerIds) {
|
|
return [];
|
|
}
|
|
|
|
$rows = Db::name($table)
|
|
->field('customer_id, COUNT(*) AS total')
|
|
->whereIn('customer_id', $customerIds)
|
|
->group('customer_id')
|
|
->select()
|
|
->toArray();
|
|
|
|
$map = [];
|
|
foreach ($rows as $row) {
|
|
$map[(int)$row['customer_id']] = (int)$row['total'];
|
|
}
|
|
return $map;
|
|
}
|
|
|
|
private function customerService(): EnterpriseCustomerService
|
|
{
|
|
return new EnterpriseCustomerService();
|
|
}
|
|
|
|
private function webhookService(): EnterpriseWebhookService
|
|
{
|
|
return new EnterpriseWebhookService();
|
|
}
|
|
}
|