CaiWuXiTong/modules/EcommerceFinance/Services/PlantingReimbursementDirectory.php
limeng f78149e630 feat(种菜报销): 接入钉钉群机器人(取消通知 @ 发起人、每天 10:25 推送汇总),看板职级为空改为取消 (#1150)
- 工作台新增「钉钉机器人设置」: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
2026-11-13 11:15:04 +08:00

94 lines
3.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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 [];
}
}
}