- 工作台新增「钉钉机器人设置」:Webhook、加签密钥加密存公司设置,页面不回显;支持自定义关键词、汇总单独发群、@ 报销人花名 - 核对后点「发取消通知到钉钉群」:按发起人一人一条,按钉钉花名册(花名 → userid)@ 发起人;同一批已发过的不重复发,可勾选重发 - 新增 planting-reimbursement:daily-digest,每天 10:25(宿主机 cron)生成当天批次,把待核对条数/金额、可报销合计、取消/待复查数、自检结果和工作台链接推到群里;数据停更或看板未上传会提醒 - 种菜监控看板提示「职级为空」的单改为取消工单,群通知原因写「职级为空」(2026-11-13 报销人确认) - 没上传花名册时,收款人在职改按钉钉花名册在职真名判断;两边都取不到时不做该项校验并提示,避免误判 涉及:只读聚工单镜像与钉钉花名册副本;钉钉群机器人发消息;不涉及迁移,不写聚工单。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VANWYsNUbu2piMtCKymoyd
94 lines
3.4 KiB
PHP
94 lines
3.4 KiB
PHP
<?php
|
||
|
||
namespace Modules\EcommerceFinance\Services;
|
||
|
||
use Illuminate\Support\Facades\Cache;
|
||
use Throwable;
|
||
|
||
/**
|
||
* 钉钉花名册(运营只读副本 rpt.hr_flowerName_book):
|
||
* - 花名 → 钉钉 userid,用于机器人 @ 发起人(钉钉里显示的就是花名);
|
||
* - 在职真名集合,没上传花名册时用来判断收款人是否在职。
|
||
* 只读查询,结果缓存,查不到时返回空、不影响页面。
|
||
*/
|
||
class PlantingReimbursementDirectory
|
||
{
|
||
private const CACHE_MINUTES = 720;
|
||
|
||
public function __construct(private readonly UpstreamReadOnlyQueryFactory $queries) {}
|
||
|
||
/**
|
||
* @param list<string> $flowerNames
|
||
* @return array<string,string> 花名 → userid(同一花名对应多人时不返回,避免 @ 错人)
|
||
*/
|
||
public function userIds(array $flowerNames): array
|
||
{
|
||
$names = array_values(array_unique(array_filter(array_map(
|
||
static fn (mixed $name): string => PlantingReimbursementAuditor::normName((string) $name),
|
||
$flowerNames,
|
||
))));
|
||
if ($names === []) {
|
||
return [];
|
||
}
|
||
|
||
$map = [];
|
||
try {
|
||
$directory = Cache::remember('planting-reimbursement:flower-userids:v1', now()->addMinutes(self::CACHE_MINUTES), function (): array {
|
||
$rows = $this->queries->forSourceWithTimeout('operations_replica', 60)->select(
|
||
'SELECT flowerName, flowerNameId FROM rpt.hr_flowerName_book
|
||
WHERE leaveDate IS NULL AND flowerNameId IS NOT NULL AND flowerName IS NOT NULL'
|
||
);
|
||
$byName = [];
|
||
foreach ($rows as $row) {
|
||
$name = PlantingReimbursementAuditor::normName((string) ($row['flowerName'] ?? ''));
|
||
$id = trim((string) ($row['flowerNameId'] ?? ''));
|
||
if ($name !== '' && $id !== '') {
|
||
$byName[$name][$id] = true;
|
||
}
|
||
}
|
||
$unique = [];
|
||
foreach ($byName as $name => $ids) {
|
||
if (count($ids) === 1) {
|
||
$unique[$name] = (string) array_key_first($ids);
|
||
}
|
||
}
|
||
|
||
return $unique;
|
||
});
|
||
foreach ($names as $name) {
|
||
if (isset($directory[$name])) {
|
||
$map[$name] = $directory[$name];
|
||
}
|
||
}
|
||
} catch (Throwable $exception) {
|
||
report($exception);
|
||
}
|
||
|
||
return $map;
|
||
}
|
||
|
||
/** @return array<string,bool> 在职员工真名集合;查询失败返回空 */
|
||
public function activeNames(): array
|
||
{
|
||
try {
|
||
return Cache::remember('planting-reimbursement:active-names:v1', now()->addMinutes(self::CACHE_MINUTES), function (): array {
|
||
$set = [];
|
||
foreach ($this->queries->forSourceWithTimeout('operations_replica', 60)->select(
|
||
'SELECT DISTINCT name FROM rpt.hr_flowerName_book WHERE leaveDate IS NULL AND name IS NOT NULL'
|
||
) as $row) {
|
||
$name = PlantingReimbursementAuditor::normName((string) ($row['name'] ?? ''));
|
||
if ($name !== '') {
|
||
$set[$name] = true;
|
||
}
|
||
}
|
||
|
||
return $set;
|
||
});
|
||
} catch (Throwable $exception) {
|
||
report($exception);
|
||
|
||
return [];
|
||
}
|
||
}
|
||
}
|