Files
zyt/server/app/adminapi/logic/finance/DeptPerformanceTargetLogic.php
T
2026-05-11 17:49:38 +08:00

238 lines
8.2 KiB
PHP
Executable File

<?php
declare(strict_types=1);
namespace app\adminapi\logic\finance;
use app\adminapi\logic\dept\DeptLogic;
use app\common\logic\BaseLogic;
use app\common\model\dept\Dept;
use app\common\model\finance\DeptPerformanceTarget;
use app\common\service\DataScope\DataScopeService;
use think\facade\Db;
class DeptPerformanceTargetLogic extends BaseLogic
{
/**
* 指定月份:保留部门树层级 + 合并当月已有目标
*
* @return array{year_month: string, rows: array<int, array<string, mixed>>, total_target: float, data_scope_limited: bool}
*/
public static function monthMatrix(string $yearMonth, int $adminId = 0, array $adminInfo = []): array
{
$tree = DeptLogic::getAllData();
$allowed = DataScopeService::getAllowedDeptIdSet($adminId, $adminInfo);
$scoped = $allowed !== null;
if ($scoped) {
$tree = self::filterDeptTreeAllowedForest(is_array($tree) ? $tree : [], $allowed);
}
$rowsDb = DeptPerformanceTarget::where('year_month', $yearMonth)
->select()
->toArray();
$targets = [];
foreach ($rowsDb as $r) {
$targets[(int) ($r['dept_id'] ?? 0)] = $r;
}
$rows = self::attachTargetsToTree(is_array($tree) ? $tree : [], $targets);
$total = self::sumTargetsInTree($rows);
return [
'year_month' => $yearMonth,
'rows' => $rows,
'total_target' => round($total, 2),
'data_scope_limited' => $scoped,
];
}
/**
* @param array<int, array{dept_id?:int|float|string, target_amount?:int|float|string, remark?:string}> $items
*/
public static function batchSave(string $yearMonth, array $items, int $adminId, array $adminInfo, string $adminName): bool
{
$allowed = DataScopeService::getAllowedDeptIdSet($adminId, $adminInfo);
try {
Db::transaction(function () use ($yearMonth, $items, $adminId, $adminName, $allowed): void {
foreach ($items as $item) {
if (!is_array($item)) {
continue;
}
$deptId = (int) ($item['dept_id'] ?? 0);
if ($deptId <= 0) {
continue;
}
// 仅处理 data scope 内的 dept_id(界面树与 monthMatrix 一致,不再含纯展示用父级)
if ($allowed !== null && !isset($allowed[$deptId])) {
continue;
}
$dept = Dept::where('id', $deptId)->whereNull('delete_time')->find();
if (!$dept) {
continue;
}
$deptName = trim((string) ($dept->name ?? ''));
$amt = round((float) ($item['target_amount'] ?? 0), 2);
$remark = mb_substr(trim((string) ($item['remark'] ?? '')), 0, 255);
if ($amt <= 0) {
DeptPerformanceTarget::where('dept_id', $deptId)
->where('year_month', $yearMonth)
->delete();
continue;
}
$row = DeptPerformanceTarget::where('dept_id', $deptId)
->where('year_month', $yearMonth)
->find();
if ($row) {
$row->dept_name = $deptName;
$row->target_amount = $amt;
$row->remark = $remark;
$row->updater_id = $adminId;
$row->updater_name = $adminName;
$row->save();
} else {
DeptPerformanceTarget::create([
'dept_id' => $deptId,
'dept_name' => $deptName,
'year_month' => $yearMonth,
'target_amount' => $amt,
'remark' => $remark,
'creator_id' => $adminId,
'creator_name' => $adminName,
'updater_id' => $adminId,
'updater_name' => $adminName,
]);
}
}
});
return true;
} catch (\Throwable $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* @param array<int, array<string, mixed>> $nodes DeptLogic::getAllData 子树
* @param array<int, array<string, mixed>> $targets keyed by dept_id
*
* @return array<int, array<string, mixed>>
*/
private static function attachTargetsToTree(array $nodes, array $targets, string $prefix = ''): array
{
$out = [];
foreach ($nodes as $n) {
if (!is_array($n)) {
continue;
}
$id = (int) ($n['id'] ?? 0);
$name = trim((string) ($n['name'] ?? ''));
if ($id <= 0) {
continue;
}
$path = $prefix === '' ? $name : $prefix . ' / ' . $name;
$t = $targets[$id] ?? null;
$amt = $t ? round((float) $t['target_amount'], 2) : 0.0;
$node = [
'dept_id' => $id,
'dept_name' => $name,
'dept_path' => $path,
'target_id' => $t ? (int) $t['id'] : 0,
'target_amount' => $amt,
'remark' => $t ? (string) ($t['remark'] ?? '') : '',
];
$rawChildren = $n['children'] ?? [];
$childList = is_array($rawChildren) && $rawChildren !== []
? self::attachTargetsToTree($rawChildren, $targets, $path)
: [];
if ($childList !== []) {
$node['children'] = $childList;
}
$out[] = $node;
}
return $out;
}
/**
* @param array<int, array<string, mixed>> $nodes
*/
private static function sumTargetsInTree(array $nodes): float
{
$s = 0.0;
foreach ($nodes as $n) {
$s += (float) ($n['target_amount'] ?? 0);
$ch = $n['children'] ?? [];
if (is_array($ch) && $ch !== []) {
$s += self::sumTargetsInTree($ch);
}
}
return $s;
}
/**
* 本部门及以下:仅展示 allowed 内节点;无权父级不渲染,从子树中接续(不露出公司/上级中心)。
*
* @param array<int, array<string, mixed>> $nodes
* @param array<int, true> $allowedMap
*
* @return array<int, array<string, mixed>>
*/
private static function filterDeptTreeAllowedForest(array $nodes, array $allowedMap): array
{
if ($allowedMap === []) {
return [];
}
$out = [];
foreach ($nodes as $n) {
if (!is_array($n)) {
continue;
}
$id = (int) ($n['id'] ?? 0);
if ($id <= 0) {
continue;
}
$rawChildren = $n['children'] ?? [];
$kids = is_array($rawChildren) ? $rawChildren : [];
if (isset($allowedMap[$id])) {
$childList = [];
foreach ($kids as $c) {
if (!is_array($c)) {
continue;
}
foreach (self::filterDeptTreeAllowedForest([$c], $allowedMap) as $item) {
$childList[] = $item;
}
}
$node = $n;
if ($childList !== []) {
$node['children'] = $childList;
} else {
unset($node['children']);
}
$out[] = $node;
continue;
}
foreach ($kids as $c) {
if (!is_array($c)) {
continue;
}
foreach (self::filterDeptTreeAllowedForest([$c], $allowedMap) as $item) {
$out[] = $item;
}
}
}
return $out;
}
}