Merge branch 'cursor/self-input-stats'
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\controller\stats;
|
||||
|
||||
use app\adminapi\controller\BaseAdminController;
|
||||
use app\adminapi\lists\stats\PersonalAccountCostLists;
|
||||
use app\adminapi\logic\stats\PersonalAccountCostLogic;
|
||||
use app\adminapi\validate\stats\PersonalAccountCostValidate;
|
||||
|
||||
class PersonalAccountCostController extends BaseAdminController
|
||||
{
|
||||
public function lists()
|
||||
{
|
||||
return $this->dataLists(new PersonalAccountCostLists());
|
||||
}
|
||||
|
||||
public function add()
|
||||
{
|
||||
$params = (new PersonalAccountCostValidate())->post()->goCheck('add');
|
||||
$result = PersonalAccountCostLogic::add($params, $this->adminId, (string) ($this->adminInfo['name'] ?? ''));
|
||||
if ($result === false) {
|
||||
return $this->fail(PersonalAccountCostLogic::getError());
|
||||
}
|
||||
|
||||
return $this->success('添加成功', [], 1, 1);
|
||||
}
|
||||
|
||||
public function edit()
|
||||
{
|
||||
$params = (new PersonalAccountCostValidate())->post()->goCheck('edit');
|
||||
$result = PersonalAccountCostLogic::edit($params, $this->adminId, (string) ($this->adminInfo['name'] ?? ''), $this->adminInfo);
|
||||
if ($result === false) {
|
||||
return $this->fail(PersonalAccountCostLogic::getError());
|
||||
}
|
||||
|
||||
return $this->success('编辑成功', [], 1, 1);
|
||||
}
|
||||
|
||||
public function detail()
|
||||
{
|
||||
$params = (new PersonalAccountCostValidate())->goCheck('detail');
|
||||
$detail = PersonalAccountCostLogic::detail((int) $params['id'], $this->adminId, $this->adminInfo);
|
||||
if ($detail === []) {
|
||||
return $this->fail('记录不存在或无权查看');
|
||||
}
|
||||
|
||||
return $this->data($detail);
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
$params = (new PersonalAccountCostValidate())->post()->goCheck('delete');
|
||||
$result = PersonalAccountCostLogic::delete((int) $params['id'], $this->adminId, $this->adminInfo);
|
||||
if ($result === false) {
|
||||
return $this->fail(PersonalAccountCostLogic::getError());
|
||||
}
|
||||
|
||||
return $this->success('删除成功', [], 1, 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\controller\stats;
|
||||
|
||||
use app\adminapi\controller\BaseAdminController;
|
||||
use app\adminapi\lists\stats\PersonalYejiLists;
|
||||
use app\adminapi\logic\stats\PersonalYejiLogic;
|
||||
use app\adminapi\validate\stats\PersonalYejiValidate;
|
||||
|
||||
class PersonalYejiController extends BaseAdminController
|
||||
{
|
||||
public function lists()
|
||||
{
|
||||
return $this->dataLists(new PersonalYejiLists());
|
||||
}
|
||||
|
||||
public function add()
|
||||
{
|
||||
$params = (new PersonalYejiValidate())->post()->goCheck('add');
|
||||
$result = PersonalYejiLogic::add($params, $this->adminId, (string) ($this->adminInfo['name'] ?? ''));
|
||||
if ($result === false) {
|
||||
return $this->fail(PersonalYejiLogic::getError());
|
||||
}
|
||||
|
||||
return $this->success('添加成功', [], 1, 1);
|
||||
}
|
||||
|
||||
public function edit()
|
||||
{
|
||||
$params = (new PersonalYejiValidate())->post()->goCheck('edit');
|
||||
$result = PersonalYejiLogic::edit($params, $this->adminId, (string) ($this->adminInfo['name'] ?? ''), $this->adminInfo);
|
||||
if ($result === false) {
|
||||
return $this->fail(PersonalYejiLogic::getError());
|
||||
}
|
||||
|
||||
return $this->success('编辑成功', [], 1, 1);
|
||||
}
|
||||
|
||||
public function detail()
|
||||
{
|
||||
$params = (new PersonalYejiValidate())->goCheck('detail');
|
||||
$detail = PersonalYejiLogic::detail((int) $params['id'], $this->adminId, $this->adminInfo);
|
||||
if ($detail === []) {
|
||||
return $this->fail('记录不存在或无权查看');
|
||||
}
|
||||
|
||||
return $this->data($detail);
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
$params = (new PersonalYejiValidate())->post()->goCheck('delete');
|
||||
$result = PersonalYejiLogic::delete((int) $params['id'], $this->adminId, $this->adminInfo);
|
||||
if ($result === false) {
|
||||
return $this->fail(PersonalYejiLogic::getError());
|
||||
}
|
||||
|
||||
return $this->success('删除成功', [], 1, 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\controller\stats;
|
||||
|
||||
use app\adminapi\controller\BaseAdminController;
|
||||
use app\adminapi\logic\stats\SelfInputLogic;
|
||||
|
||||
class SelfInputController extends BaseAdminController
|
||||
{
|
||||
public function overview()
|
||||
{
|
||||
$result = SelfInputLogic::overview($this->request->get(), $this->adminId, $this->adminInfo);
|
||||
|
||||
return $this->data($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自媒体来源下拉:从已录入业绩/账户消耗去重提取(随录入动态变化)
|
||||
*/
|
||||
public function mediaSourceOptions()
|
||||
{
|
||||
$list = SelfInputLogic::mediaSourceOptions($this->adminId, $this->adminInfo);
|
||||
|
||||
return $this->data($list);
|
||||
}
|
||||
}
|
||||
@@ -132,6 +132,19 @@ class AuthMiddleware
|
||||
return true;
|
||||
}
|
||||
|
||||
// 自录转化统计:自媒体来源下拉,复用总览或账户消耗列表权限
|
||||
if ($accessUri === 'stats.selfinput/mediasourceoptions') {
|
||||
$selfInputAliases = [
|
||||
'stats.selfinput/overview',
|
||||
'stats.self_input/overview',
|
||||
'stats.personalaccountcost/lists',
|
||||
'stats.personal_account_cost/lists',
|
||||
];
|
||||
if (count(array_intersect($selfInputAliases, $AdminUris)) > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 处方库列表:消费者开方页/处方库页导入共用,复用开方或处方库菜单权限
|
||||
if ($accessUri === 'tcm.prescriptionlibrary/lists'
|
||||
&& $this->matchPrescriptionLibraryListsPermission($adminUris)) {
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\lists\stats;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\adminapi\logic\stats\PersonalStatsScopeTrait;
|
||||
use app\common\lists\ListsExtendInterface;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\lists\Traits\HasDataScopeFilter;
|
||||
use app\common\model\stats\PersonalAccountCost;
|
||||
|
||||
class PersonalAccountCostLists extends BaseAdminDataLists implements ListsSearchInterface, ListsExtendInterface
|
||||
{
|
||||
use HasDataScopeFilter;
|
||||
use PersonalStatsScopeTrait;
|
||||
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'%like%' => ['media_source', 'creator_name', 'remark'],
|
||||
];
|
||||
}
|
||||
|
||||
private function baseQuery()
|
||||
{
|
||||
$query = PersonalAccountCost::where($this->searchWhere);
|
||||
$visibleIds = $this->getDataScopeVisibleAdminIds();
|
||||
$deptId = (int) ($this->params['dept_id'] ?? 0);
|
||||
|
||||
$finalIds = self::intersectVisibleByDept($visibleIds, $deptId);
|
||||
if ($finalIds === []) {
|
||||
$query->whereRaw('0 = 1');
|
||||
} elseif ($finalIds !== null) {
|
||||
$query->whereIn('creator_id', $finalIds);
|
||||
}
|
||||
|
||||
if (!empty($this->params['start_date']) && !empty($this->params['end_date'])) {
|
||||
$query->whereBetween('cost_date', [$this->params['start_date'], $this->params['end_date']]);
|
||||
} elseif (!empty($this->params['start_date'])) {
|
||||
$query->where('cost_date', '>=', $this->params['start_date']);
|
||||
} elseif (!empty($this->params['end_date'])) {
|
||||
$query->where('cost_date', '<=', $this->params['end_date']);
|
||||
}
|
||||
|
||||
if (!empty($this->params['media_source'])) {
|
||||
$query->where('media_source', trim((string) $this->params['media_source']));
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function lists(): array
|
||||
{
|
||||
$rows = $this->baseQuery()
|
||||
->order(['cost_date' => 'desc', 'id' => 'desc'])
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
return self::attachDeptInfoToRows($rows);
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return (int) $this->baseQuery()->count();
|
||||
}
|
||||
|
||||
public function extend(): array
|
||||
{
|
||||
return [
|
||||
'total_amount' => round((float) $this->baseQuery()->sum('amount'), 2),
|
||||
'days_count' => (int) $this->baseQuery()->distinct(true)->count('cost_date'),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\lists\stats;
|
||||
|
||||
use app\adminapi\lists\BaseAdminDataLists;
|
||||
use app\adminapi\logic\stats\PersonalStatsScopeTrait;
|
||||
use app\common\lists\ListsSearchInterface;
|
||||
use app\common\lists\Traits\HasDataScopeFilter;
|
||||
use app\common\model\stats\PersonalYeji;
|
||||
|
||||
class PersonalYejiLists extends BaseAdminDataLists implements ListsSearchInterface
|
||||
{
|
||||
use HasDataScopeFilter;
|
||||
use PersonalStatsScopeTrait;
|
||||
|
||||
public function setSearch(): array
|
||||
{
|
||||
return [
|
||||
'%like%' => ['media_source', 'creator_name', 'remark'],
|
||||
];
|
||||
}
|
||||
|
||||
private function baseQuery()
|
||||
{
|
||||
$query = PersonalYeji::where($this->searchWhere);
|
||||
$visibleIds = $this->getDataScopeVisibleAdminIds();
|
||||
$deptId = (int) ($this->params['dept_id'] ?? 0);
|
||||
|
||||
$finalIds = self::intersectVisibleByDept($visibleIds, $deptId);
|
||||
if ($finalIds === []) {
|
||||
$query->whereRaw('0 = 1');
|
||||
} elseif ($finalIds !== null) {
|
||||
$query->whereIn('creator_id', $finalIds);
|
||||
}
|
||||
|
||||
if (!empty($this->params['start_date']) && !empty($this->params['end_date'])) {
|
||||
$query->whereBetween('yeji_date', [$this->params['start_date'], $this->params['end_date']]);
|
||||
} elseif (!empty($this->params['start_date'])) {
|
||||
$query->where('yeji_date', '>=', $this->params['start_date']);
|
||||
} elseif (!empty($this->params['end_date'])) {
|
||||
$query->where('yeji_date', '<=', $this->params['end_date']);
|
||||
}
|
||||
|
||||
if (!empty($this->params['media_source'])) {
|
||||
$query->where('media_source', trim((string) $this->params['media_source']));
|
||||
}
|
||||
|
||||
if (!empty($this->params['creator_id'])) {
|
||||
$query->where('creator_id', (int) $this->params['creator_id']);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function lists(): array
|
||||
{
|
||||
$rows = $this->baseQuery()
|
||||
->order(['yeji_date' => 'desc', 'id' => 'desc'])
|
||||
->limit($this->limitOffset, $this->limitLength)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
return self::attachDeptInfoToRows($rows);
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return (int) $this->baseQuery()->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\logic\stats;
|
||||
|
||||
use app\common\logic\BaseLogic;
|
||||
use app\common\model\stats\PersonalAccountCost;
|
||||
|
||||
class PersonalAccountCostLogic extends BaseLogic
|
||||
{
|
||||
use PersonalStatsScopeTrait;
|
||||
|
||||
public static function add(array $params, int $adminId, string $adminName): bool
|
||||
{
|
||||
try {
|
||||
$costDate = (string) $params['cost_date'];
|
||||
$mediaSource = self::normalizeMediaSource((string) ($params['media_source'] ?? ''));
|
||||
if ($mediaSource === '') {
|
||||
self::setError('请填写自媒体来源');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$conflict = self::findCostConflict($costDate, $mediaSource);
|
||||
if ($conflict['conflict']) {
|
||||
$by = $conflict['creator_name'] !== '' ? '(录入人:' . $conflict['creator_name'] . ')' : '';
|
||||
self::setError('该日期下该渠道的账户消耗已存在' . $by . ',请直接编辑该记录,避免重复汇总');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
PersonalAccountCost::create([
|
||||
'cost_date' => $costDate,
|
||||
'media_source' => $mediaSource,
|
||||
'amount' => round((float) ($params['amount'] ?? 0), 2),
|
||||
'remark' => (string) ($params['remark'] ?? ''),
|
||||
'creator_id' => $adminId,
|
||||
'creator_name' => $adminName,
|
||||
'updater_id' => $adminId,
|
||||
'updater_name' => $adminName,
|
||||
'dept_id' => self::resolvePrimaryDeptId($adminId),
|
||||
]);
|
||||
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
self::setError($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function edit(array $params, int $adminId, string $adminName, array $adminInfo): bool
|
||||
{
|
||||
try {
|
||||
$model = PersonalAccountCost::find($params['id']);
|
||||
if (!$model) {
|
||||
self::setError('记录不存在');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!self::assertRecordVisible($adminId, $adminInfo, (int) $model->creator_id)) {
|
||||
self::setError('无权操作该记录');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$model->amount = round((float) ($params['amount'] ?? 0), 2);
|
||||
$model->remark = (string) ($params['remark'] ?? '');
|
||||
$model->updater_id = $adminId;
|
||||
$model->updater_name = $adminName;
|
||||
$model->save();
|
||||
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
self::setError($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function delete(int $id, int $adminId, array $adminInfo): bool
|
||||
{
|
||||
try {
|
||||
$model = PersonalAccountCost::find($id);
|
||||
if (!$model) {
|
||||
self::setError('记录不存在');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!self::assertRecordVisible($adminId, $adminInfo, (int) $model->creator_id)) {
|
||||
self::setError('无权操作该记录');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$model->delete();
|
||||
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
self::setError($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function detail(int $id, int $adminId, array $adminInfo): array
|
||||
{
|
||||
$model = PersonalAccountCost::find($id);
|
||||
if (!$model) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!self::assertRecordVisible($adminId, $adminInfo, (int) $model->creator_id)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $model->toArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\logic\stats;
|
||||
|
||||
use app\adminapi\logic\dept\DeptLogic;
|
||||
use app\common\model\auth\AdminDept;
|
||||
use app\common\model\dept\Dept;
|
||||
use app\common\model\stats\PersonalAccountCost;
|
||||
use app\common\model\stats\PersonalYeji;
|
||||
use app\common\service\DataScope\DataScopeService;
|
||||
use think\facade\Config;
|
||||
|
||||
trait PersonalStatsScopeTrait
|
||||
{
|
||||
protected static function resolvePrimaryDeptId(int $adminId): int
|
||||
{
|
||||
if ($adminId <= 0) {
|
||||
return 0;
|
||||
}
|
||||
$deptId = AdminDept::where('admin_id', $adminId)->value('dept_id');
|
||||
|
||||
return (int) ($deptId ?: 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, int> $creatorIds
|
||||
* @return array{0: array<int, int>, 1: array<int, string>, 2: array<int, string>}
|
||||
* [adminId => deptId, deptId => name, deptId => "祖/父/当前"]
|
||||
*/
|
||||
protected static function loadAdminDeptMap(array $creatorIds): array
|
||||
{
|
||||
$creatorIds = array_values(array_unique(array_filter(array_map('intval', $creatorIds))));
|
||||
if ($creatorIds === []) {
|
||||
return [[], [], []];
|
||||
}
|
||||
$rows = AdminDept::whereIn('admin_id', $creatorIds)
|
||||
->field('admin_id, dept_id')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$adminToDeptId = [];
|
||||
foreach ($rows as $row) {
|
||||
$adminId = (int) ($row['admin_id'] ?? 0);
|
||||
$deptId = (int) ($row['dept_id'] ?? 0);
|
||||
if ($adminId <= 0 || $deptId <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($adminToDeptId[$adminId])) {
|
||||
$adminToDeptId[$adminId] = $deptId;
|
||||
}
|
||||
}
|
||||
|
||||
if ($adminToDeptId === []) {
|
||||
return [[], [], []];
|
||||
}
|
||||
|
||||
$allDeptRows = Dept::field('id, pid, name')->select()->toArray();
|
||||
$deptIndex = [];
|
||||
foreach ($allDeptRows as $row) {
|
||||
$id = (int) ($row['id'] ?? 0);
|
||||
if ($id <= 0) {
|
||||
continue;
|
||||
}
|
||||
$deptIndex[$id] = [
|
||||
'pid' => (int) ($row['pid'] ?? 0),
|
||||
'name' => (string) ($row['name'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
$deptNameMap = [];
|
||||
$deptPathMap = [];
|
||||
foreach (array_unique(array_values($adminToDeptId)) as $deptId) {
|
||||
$deptId = (int) $deptId;
|
||||
if ($deptId <= 0 || !isset($deptIndex[$deptId])) {
|
||||
continue;
|
||||
}
|
||||
$deptNameMap[$deptId] = $deptIndex[$deptId]['name'];
|
||||
$deptPathMap[$deptId] = self::resolveDeptPath($deptId, $deptIndex);
|
||||
}
|
||||
|
||||
return [$adminToDeptId, $deptNameMap, $deptPathMap];
|
||||
}
|
||||
|
||||
/**
|
||||
* 从根节点到当前部门的完整链路(用 / 分隔)。
|
||||
*
|
||||
* @param array<int, array{pid: int, name: string}> $deptIndex
|
||||
*/
|
||||
private static function resolveDeptPath(int $deptId, array $deptIndex): string
|
||||
{
|
||||
$names = [];
|
||||
$guard = 0;
|
||||
$cursor = $deptId;
|
||||
while ($cursor > 0 && isset($deptIndex[$cursor]) && $guard++ < 32) {
|
||||
$node = $deptIndex[$cursor];
|
||||
$name = trim($node['name']);
|
||||
if ($name !== '') {
|
||||
array_unshift($names, $name);
|
||||
}
|
||||
$cursor = $node['pid'];
|
||||
}
|
||||
|
||||
return implode(' / ', $names);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $rows
|
||||
* @return array<int, array<string, mixed>>
|
||||
*/
|
||||
protected static function attachDeptInfoToRows(array $rows): array
|
||||
{
|
||||
if ($rows === []) {
|
||||
return $rows;
|
||||
}
|
||||
$creatorIds = array_map(static fn (array $row): int => (int) ($row['creator_id'] ?? 0), $rows);
|
||||
[$adminToDeptId, $deptNameMap, $deptPathMap] = self::loadAdminDeptMap($creatorIds);
|
||||
foreach ($rows as &$row) {
|
||||
$creatorId = (int) ($row['creator_id'] ?? 0);
|
||||
$deptId = $adminToDeptId[$creatorId] ?? 0;
|
||||
$row['dept_id'] = $deptId;
|
||||
$row['dept_name'] = $deptId > 0 ? (string) ($deptNameMap[$deptId] ?? '') : '';
|
||||
$row['dept_path'] = $deptId > 0 ? (string) ($deptPathMap[$deptId] ?? '') : '';
|
||||
}
|
||||
unset($row);
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 dept_id(包含其子部门)收窄可见 admin id 集合。
|
||||
* 与 visibleAdminIds 取交集。
|
||||
*
|
||||
* @param array<int>|null $visibleAdminIds null 表示不限
|
||||
* @return array<int>|null 返回 null 表示外部条件无需附加;返回 [] 表示无可见 admin
|
||||
*/
|
||||
protected static function intersectVisibleByDept(?array $visibleAdminIds, int $deptId): ?array
|
||||
{
|
||||
if ($deptId <= 0) {
|
||||
return $visibleAdminIds;
|
||||
}
|
||||
$deptIds = DeptLogic::getSelfAndDescendantIds($deptId);
|
||||
if ($deptIds === []) {
|
||||
$deptIds = [$deptId];
|
||||
}
|
||||
$adminIds = AdminDept::whereIn('dept_id', $deptIds)->column('admin_id');
|
||||
$adminIds = array_values(array_unique(array_filter(array_map('intval', $adminIds), static fn (int $v): bool => $v > 0)));
|
||||
|
||||
if ($visibleAdminIds === null) {
|
||||
return $adminIds;
|
||||
}
|
||||
|
||||
return array_values(array_intersect($visibleAdminIds, $adminIds));
|
||||
}
|
||||
|
||||
protected static function normalizeMediaSource(string $mediaSource): string
|
||||
{
|
||||
return trim($mediaSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* 与 project.self_input_stats_view_all_roles 一致:超管或白名单角色可见全部录入人数据。
|
||||
*/
|
||||
protected static function canViewAllSelfInputStats(array $adminInfo): bool
|
||||
{
|
||||
if ((int) ($adminInfo['root'] ?? 0) === 1) {
|
||||
return true;
|
||||
}
|
||||
$allow = Config::get('project.self_input_stats_view_all_roles', []);
|
||||
$allow = array_map('intval', is_array($allow) ? $allow : []);
|
||||
if ($allow === []) {
|
||||
return false;
|
||||
}
|
||||
$myRoles = array_map('intval', $adminInfo['role_id'] ?? []);
|
||||
|
||||
return count(array_intersect($myRoles, $allow)) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int>|null null=全部录入人;[]=无可见;int[]=可见 creator_id 集合
|
||||
*/
|
||||
protected static function getVisibleCreatorIds(int $adminId, array $adminInfo): ?array
|
||||
{
|
||||
if (self::canViewAllSelfInputStats($adminInfo)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return DataScopeService::getVisibleAdminIds($adminId, $adminInfo);
|
||||
}
|
||||
|
||||
protected static function assertRecordVisible(int $adminId, array $adminInfo, int $creatorId): bool
|
||||
{
|
||||
$visibleIds = self::getVisibleCreatorIds($adminId, $adminInfo);
|
||||
if ($visibleIds === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return in_array($creatorId, $visibleIds, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同一天 + 同一渠道全局唯一(不分录入人):避免重复汇总。
|
||||
* 命中时返回首个已存在记录的录入人姓名,便于前端提示。
|
||||
*
|
||||
* @return array{conflict: bool, creator_name: string}
|
||||
*/
|
||||
protected static function findYejiConflict(string $yejiDate, string $mediaSource, int $excludeId = 0): array
|
||||
{
|
||||
$query = PersonalYeji::where('yeji_date', $yejiDate)
|
||||
->where('media_source', $mediaSource);
|
||||
if ($excludeId > 0) {
|
||||
$query->where('id', '<>', $excludeId);
|
||||
}
|
||||
$row = $query->field('id, creator_name')->find();
|
||||
if (!$row) {
|
||||
return ['conflict' => false, 'creator_name' => ''];
|
||||
}
|
||||
|
||||
return ['conflict' => true, 'creator_name' => (string) ($row['creator_name'] ?? '')];
|
||||
}
|
||||
|
||||
/**
|
||||
* 同一天 + 同一渠道全局唯一(不分录入人):避免重复汇总。
|
||||
*
|
||||
* @return array{conflict: bool, creator_name: string}
|
||||
*/
|
||||
protected static function findCostConflict(string $costDate, string $mediaSource, int $excludeId = 0): array
|
||||
{
|
||||
$query = PersonalAccountCost::where('cost_date', $costDate)
|
||||
->where('media_source', $mediaSource);
|
||||
if ($excludeId > 0) {
|
||||
$query->where('id', '<>', $excludeId);
|
||||
}
|
||||
$row = $query->field('id, creator_name')->find();
|
||||
if (!$row) {
|
||||
return ['conflict' => false, 'creator_name' => ''];
|
||||
}
|
||||
|
||||
return ['conflict' => true, 'creator_name' => (string) ($row['creator_name'] ?? '')];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\logic\stats;
|
||||
|
||||
use app\common\logic\BaseLogic;
|
||||
use app\common\model\stats\PersonalYeji;
|
||||
|
||||
class PersonalYejiLogic extends BaseLogic
|
||||
{
|
||||
use PersonalStatsScopeTrait;
|
||||
|
||||
public static function add(array $params, int $adminId, string $adminName): bool
|
||||
{
|
||||
try {
|
||||
$yejiDate = (string) $params['yeji_date'];
|
||||
$mediaSource = self::normalizeMediaSource((string) ($params['media_source'] ?? ''));
|
||||
if ($mediaSource === '') {
|
||||
self::setError('请填写自媒体来源');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$conflict = self::findYejiConflict($yejiDate, $mediaSource);
|
||||
if ($conflict['conflict']) {
|
||||
$by = $conflict['creator_name'] !== '' ? '(录入人:' . $conflict['creator_name'] . ')' : '';
|
||||
self::setError('该日期下该渠道的业绩已存在' . $by . ',请直接编辑该记录,避免重复汇总');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
PersonalYeji::create([
|
||||
'yeji_date' => $yejiDate,
|
||||
'media_source' => $mediaSource,
|
||||
'add_fans_count' => (int) ($params['add_fans_count'] ?? 0),
|
||||
'total_open_count' => (int) ($params['total_open_count'] ?? 0),
|
||||
'unreplied_count' => (int) ($params['unreplied_count'] ?? 0),
|
||||
'paid_appointment_count' => (int) ($params['paid_appointment_count'] ?? 0),
|
||||
'free_appointment_count' => (int) ($params['free_appointment_count'] ?? 0),
|
||||
'interview_count' => (int) ($params['interview_count'] ?? 0),
|
||||
'order_amount' => round((float) ($params['order_amount'] ?? 0), 2),
|
||||
'completed_order_count' => (int) ($params['completed_order_count'] ?? 0),
|
||||
'remark' => (string) ($params['remark'] ?? ''),
|
||||
'creator_id' => $adminId,
|
||||
'creator_name' => $adminName,
|
||||
'updater_id' => $adminId,
|
||||
'updater_name' => $adminName,
|
||||
'dept_id' => self::resolvePrimaryDeptId($adminId),
|
||||
]);
|
||||
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
self::setError($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function edit(array $params, int $adminId, string $adminName, array $adminInfo): bool
|
||||
{
|
||||
try {
|
||||
$model = PersonalYeji::find($params['id']);
|
||||
if (!$model) {
|
||||
self::setError('记录不存在');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!self::assertRecordVisible($adminId, $adminInfo, (int) $model->creator_id)) {
|
||||
self::setError('无权操作该记录');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$model->add_fans_count = (int) ($params['add_fans_count'] ?? 0);
|
||||
$model->total_open_count = (int) ($params['total_open_count'] ?? 0);
|
||||
$model->unreplied_count = (int) ($params['unreplied_count'] ?? 0);
|
||||
$model->paid_appointment_count = (int) ($params['paid_appointment_count'] ?? 0);
|
||||
$model->free_appointment_count = (int) ($params['free_appointment_count'] ?? 0);
|
||||
$model->interview_count = (int) ($params['interview_count'] ?? 0);
|
||||
$model->order_amount = round((float) ($params['order_amount'] ?? 0), 2);
|
||||
$model->completed_order_count = (int) ($params['completed_order_count'] ?? 0);
|
||||
$model->remark = (string) ($params['remark'] ?? '');
|
||||
$model->updater_id = $adminId;
|
||||
$model->updater_name = $adminName;
|
||||
$model->save();
|
||||
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
self::setError($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function delete(int $id, int $adminId, array $adminInfo): bool
|
||||
{
|
||||
try {
|
||||
$model = PersonalYeji::find($id);
|
||||
if (!$model) {
|
||||
self::setError('记录不存在');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!self::assertRecordVisible($adminId, $adminInfo, (int) $model->creator_id)) {
|
||||
self::setError('无权操作该记录');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$model->delete();
|
||||
|
||||
return true;
|
||||
} catch (\Throwable $e) {
|
||||
self::setError($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function detail(int $id, int $adminId, array $adminInfo): array
|
||||
{
|
||||
$model = PersonalYeji::find($id);
|
||||
if (!$model) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!self::assertRecordVisible($adminId, $adminInfo, (int) $model->creator_id)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $model->toArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,384 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\logic\stats;
|
||||
|
||||
use app\common\logic\BaseLogic;
|
||||
use app\common\model\dict\DictData;
|
||||
use app\common\model\stats\PersonalAccountCost;
|
||||
use app\common\model\stats\PersonalYeji;
|
||||
|
||||
class SelfInputLogic extends BaseLogic
|
||||
{
|
||||
use PersonalStatsScopeTrait;
|
||||
|
||||
public static function overview(array $params, int $adminId, array $adminInfo): array
|
||||
{
|
||||
[$startDate, $endDate] = self::resolveTimeRange($params);
|
||||
$pageNo = max(1, (int) ($params['page_no'] ?? 1));
|
||||
$pageSize = min(100, max(1, (int) ($params['page_size'] ?? 15)));
|
||||
$mediaSource = trim((string) ($params['media_source'] ?? ''));
|
||||
$deptId = (int) ($params['dept_id'] ?? 0);
|
||||
|
||||
$effectiveAdminIds = self::resolveEffectiveAdminIds($adminId, $adminInfo, $deptId);
|
||||
|
||||
$yejiQuery = self::buildYejiQuery($startDate, $endDate, $effectiveAdminIds);
|
||||
if ($mediaSource !== '') {
|
||||
$yejiQuery->where('media_source', $mediaSource);
|
||||
}
|
||||
|
||||
$count = (int) (clone $yejiQuery)->count();
|
||||
$rows = (clone $yejiQuery)
|
||||
->order(['yeji_date' => 'desc', 'id' => 'desc'])
|
||||
->page($pageNo, $pageSize)
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$costMap = self::loadAccountCostMap($startDate, $endDate, $effectiveAdminIds, $mediaSource);
|
||||
|
||||
$lists = [];
|
||||
foreach ($rows as $row) {
|
||||
$entity = self::normalizeYejiRow($row);
|
||||
$costKey = self::buildCostKey(
|
||||
(int) $entity['creator_id'],
|
||||
(string) $entity['yeji_date'],
|
||||
(string) $entity['media_source']
|
||||
);
|
||||
$entity['account_cost'] = round((float) ($costMap[$costKey] ?? 0), 2);
|
||||
$lists[] = self::finalizeMetrics($entity);
|
||||
}
|
||||
$lists = self::attachDeptInfoToRows($lists);
|
||||
|
||||
$allYejiRows = self::buildYejiQuery($startDate, $endDate, $effectiveAdminIds);
|
||||
if ($mediaSource !== '') {
|
||||
$allYejiRows->where('media_source', $mediaSource);
|
||||
}
|
||||
$allYeji = $allYejiRows->select()->toArray();
|
||||
|
||||
$summaryCostMap = $costMap;
|
||||
$summaryBase = self::emptyMetrics();
|
||||
foreach ($allYeji as $row) {
|
||||
$entity = self::normalizeYejiRow($row);
|
||||
$costKey = self::buildCostKey(
|
||||
(int) $entity['creator_id'],
|
||||
(string) $entity['yeji_date'],
|
||||
(string) $entity['media_source']
|
||||
);
|
||||
$entity['account_cost'] = round((float) ($summaryCostMap[$costKey] ?? 0), 2);
|
||||
unset($summaryCostMap[$costKey]);
|
||||
$entity = self::finalizeMetrics($entity);
|
||||
$summaryBase = self::accumulateMetrics($summaryBase, $entity);
|
||||
}
|
||||
|
||||
foreach ($summaryCostMap as $amount) {
|
||||
$summaryBase['account_cost'] += round((float) $amount, 2);
|
||||
}
|
||||
|
||||
$summary = self::finalizeMetrics($summaryBase);
|
||||
$canViewFinance = self::canViewAllSelfInputStats($adminInfo);
|
||||
|
||||
if (!$canViewFinance) {
|
||||
$summary = self::maskFinanceFields($summary);
|
||||
foreach ($lists as &$item) {
|
||||
$item = self::maskFinanceFields($item);
|
||||
}
|
||||
unset($item);
|
||||
}
|
||||
|
||||
return [
|
||||
'summary' => $summary,
|
||||
'lists' => $lists,
|
||||
'count' => $count,
|
||||
'page_no' => $pageNo,
|
||||
'page_size' => $pageSize,
|
||||
'extend' => [
|
||||
'summary' => $summary,
|
||||
'date_range' => [$startDate, $endDate],
|
||||
'can_view_finance' => $canViewFinance,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 财务相关字段(账户消耗 / 现金成本 / ROI):非豁免角色不可见,剔除字段避免被嗅探。
|
||||
*
|
||||
* @param array<string, mixed> $entity
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function maskFinanceFields(array $entity): array
|
||||
{
|
||||
foreach (['account_cost', 'cash_cost', 'roi'] as $key) {
|
||||
unset($entity[$key]);
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自媒体来源选项:来自字典「推广渠道」(type_value=channels),按 sort/id 排序。
|
||||
* 用 dict_data.name 作为存储值(与历史录入兼容;保留 value 仅作展示标识)。
|
||||
*
|
||||
* @return array<int, array{name: string, value: string}>
|
||||
*/
|
||||
public static function mediaSourceOptions(int $adminId, array $adminInfo): array
|
||||
{
|
||||
unset($adminId, $adminInfo);
|
||||
|
||||
$rows = DictData::where('type_value', 'channels')
|
||||
->where('status', 1)
|
||||
->order(['sort' => 'desc', 'id' => 'asc'])
|
||||
->field('name, value')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$list = [];
|
||||
$seen = [];
|
||||
foreach ($rows as $row) {
|
||||
$name = self::normalizeMediaSource((string) ($row['name'] ?? ''));
|
||||
if ($name === '' || isset($seen[$name])) {
|
||||
continue;
|
||||
}
|
||||
$seen[$name] = true;
|
||||
$list[] = [
|
||||
'name' => $name,
|
||||
'value' => (string) ($row['value'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int>|null null = 不限;[] = 无可见
|
||||
*/
|
||||
private static function resolveEffectiveAdminIds(int $adminId, array $adminInfo, int $deptId): ?array
|
||||
{
|
||||
$visibleIds = self::getVisibleCreatorIds($adminId, $adminInfo);
|
||||
|
||||
return self::intersectVisibleByDept($visibleIds, $deptId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int>|null $effectiveAdminIds
|
||||
*/
|
||||
private static function buildYejiQuery(string $startDate, string $endDate, ?array $effectiveAdminIds)
|
||||
{
|
||||
$query = PersonalYeji::whereBetween('yeji_date', [$startDate, $endDate]);
|
||||
if ($effectiveAdminIds === []) {
|
||||
$query->whereRaw('0 = 1');
|
||||
} elseif ($effectiveAdminIds !== null) {
|
||||
$query->whereIn('creator_id', $effectiveAdminIds);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int>|null $effectiveAdminIds
|
||||
* @return array<string, float>
|
||||
*/
|
||||
private static function loadAccountCostMap(
|
||||
string $startDate,
|
||||
string $endDate,
|
||||
?array $effectiveAdminIds,
|
||||
string $mediaSource
|
||||
): array {
|
||||
$query = PersonalAccountCost::whereBetween('cost_date', [$startDate, $endDate]);
|
||||
if ($effectiveAdminIds === []) {
|
||||
return [];
|
||||
}
|
||||
if ($effectiveAdminIds !== null) {
|
||||
$query->whereIn('creator_id', $effectiveAdminIds);
|
||||
}
|
||||
if ($mediaSource !== '') {
|
||||
$query->where('media_source', $mediaSource);
|
||||
}
|
||||
|
||||
$rows = $query
|
||||
->fieldRaw('creator_id, cost_date, media_source, SUM(amount) AS total_amount')
|
||||
->group('creator_id, cost_date, media_source')
|
||||
->select()
|
||||
->toArray();
|
||||
|
||||
$map = [];
|
||||
foreach ($rows as $row) {
|
||||
$key = self::buildCostKey(
|
||||
(int) $row['creator_id'],
|
||||
(string) $row['cost_date'],
|
||||
(string) $row['media_source']
|
||||
);
|
||||
$map[$key] = round((float) ($row['total_amount'] ?? 0), 2);
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
private static function buildCostKey(int $creatorId, string $date, string $mediaSource): string
|
||||
{
|
||||
return $creatorId . '|' . $date . '|' . self::normalizeMediaSource($mediaSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $row
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function normalizeYejiRow(array $row): array
|
||||
{
|
||||
return [
|
||||
'id' => (int) ($row['id'] ?? 0),
|
||||
'yeji_date' => (string) ($row['yeji_date'] ?? ''),
|
||||
'media_source' => (string) ($row['media_source'] ?? ''),
|
||||
'creator_id' => (int) ($row['creator_id'] ?? 0),
|
||||
'creator_name' => (string) ($row['creator_name'] ?? ''),
|
||||
'remark' => (string) ($row['remark'] ?? ''),
|
||||
'add_fans_count' => (int) ($row['add_fans_count'] ?? 0),
|
||||
'total_open_count' => (int) ($row['total_open_count'] ?? 0),
|
||||
'unreplied_count' => (int) ($row['unreplied_count'] ?? 0),
|
||||
'paid_appointment_count' => (int) ($row['paid_appointment_count'] ?? 0),
|
||||
'free_appointment_count' => (int) ($row['free_appointment_count'] ?? 0),
|
||||
'interview_count' => (int) ($row['interview_count'] ?? 0),
|
||||
'order_amount' => round((float) ($row['order_amount'] ?? 0), 2),
|
||||
'completed_order_count' => (int) ($row['completed_order_count'] ?? 0),
|
||||
'account_cost' => 0.0,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function emptyMetrics(): array
|
||||
{
|
||||
return [
|
||||
'add_fans_count' => 0,
|
||||
'total_open_count' => 0,
|
||||
'unreplied_count' => 0,
|
||||
'paid_appointment_count' => 0,
|
||||
'free_appointment_count' => 0,
|
||||
'appointment_total_count' => 0,
|
||||
'interview_count' => 0,
|
||||
'order_amount' => 0.0,
|
||||
'completed_order_count' => 0,
|
||||
'account_cost' => 0.0,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $base
|
||||
* @param array<string, mixed> $row
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function accumulateMetrics(array $base, array $row): array
|
||||
{
|
||||
$base['add_fans_count'] += (int) ($row['add_fans_count'] ?? 0);
|
||||
$base['total_open_count'] += (int) ($row['total_open_count'] ?? 0);
|
||||
$base['unreplied_count'] += (int) ($row['unreplied_count'] ?? 0);
|
||||
$base['paid_appointment_count'] += (int) ($row['paid_appointment_count'] ?? 0);
|
||||
$base['free_appointment_count'] += (int) ($row['free_appointment_count'] ?? 0);
|
||||
$base['interview_count'] += (int) ($row['interview_count'] ?? 0);
|
||||
$base['order_amount'] = round((float) $base['order_amount'] + (float) ($row['order_amount'] ?? 0), 2);
|
||||
$base['completed_order_count'] += (int) ($row['completed_order_count'] ?? 0);
|
||||
$base['account_cost'] = round((float) $base['account_cost'] + (float) ($row['account_cost'] ?? 0), 2);
|
||||
|
||||
return $base;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $entity
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function finalizeMetrics(array $entity): array
|
||||
{
|
||||
$paidAppointmentCount = (int) ($entity['paid_appointment_count'] ?? 0);
|
||||
$freeAppointmentCount = (int) ($entity['free_appointment_count'] ?? 0);
|
||||
$appointmentTotalCount = $paidAppointmentCount + $freeAppointmentCount;
|
||||
$interviewCount = (int) ($entity['interview_count'] ?? 0);
|
||||
$addFansCount = (int) ($entity['add_fans_count'] ?? 0);
|
||||
$totalOpenCount = (int) ($entity['total_open_count'] ?? 0);
|
||||
$completedOrderCount = (int) ($entity['completed_order_count'] ?? 0);
|
||||
$orderAmount = round((float) ($entity['order_amount'] ?? 0), 2);
|
||||
$accountCost = round((float) ($entity['account_cost'] ?? 0), 2);
|
||||
|
||||
$entity['appointment_total_count'] = $appointmentTotalCount;
|
||||
$entity['order_amount'] = $orderAmount;
|
||||
$entity['account_cost'] = $accountCost;
|
||||
$entity['paid_appointment_rate'] = self::percent($paidAppointmentCount, $addFansCount);
|
||||
$entity['open_appointment_rate'] = self::percent($paidAppointmentCount, $totalOpenCount);
|
||||
$entity['interview_rate'] = self::percent($interviewCount, $appointmentTotalCount);
|
||||
$entity['receive_rate'] = self::percent($completedOrderCount, $addFansCount);
|
||||
$entity['interview_receive_rate'] = self::percent($completedOrderCount, $interviewCount);
|
||||
$entity['open_receive_rate'] = self::percent($completedOrderCount, $totalOpenCount);
|
||||
$entity['avg_unit_price'] = self::safeDivideMoney($orderAmount, $completedOrderCount);
|
||||
$entity['cash_cost'] = self::safeDivideMoney($accountCost, $addFansCount);
|
||||
$entity['roi'] = self::safeDivideRatio($orderAmount, $accountCost);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{0: string, 1: string}
|
||||
*/
|
||||
private static function resolveTimeRange(array $params): array
|
||||
{
|
||||
$today = date('Y-m-d');
|
||||
$timeType = (string) ($params['time_type'] ?? 'today');
|
||||
|
||||
switch ($timeType) {
|
||||
case 'yesterday':
|
||||
$startDate = date('Y-m-d', strtotime('-1 day'));
|
||||
$endDate = $startDate;
|
||||
break;
|
||||
case 'week':
|
||||
$startDate = date('Y-m-d', strtotime('-6 days'));
|
||||
$endDate = $today;
|
||||
break;
|
||||
case 'month':
|
||||
$startDate = date('Y-m-d', strtotime('-29 days'));
|
||||
$endDate = $today;
|
||||
break;
|
||||
case 'custom':
|
||||
$startDate = trim((string) ($params['start_date'] ?? ''));
|
||||
$endDate = trim((string) ($params['end_date'] ?? ''));
|
||||
if ($startDate === '' || $endDate === '') {
|
||||
$startDate = $today;
|
||||
$endDate = $today;
|
||||
} elseif ($startDate > $endDate) {
|
||||
[$startDate, $endDate] = [$endDate, $startDate];
|
||||
}
|
||||
break;
|
||||
case 'today':
|
||||
default:
|
||||
$startDate = $today;
|
||||
$endDate = $today;
|
||||
}
|
||||
|
||||
return [$startDate, $endDate];
|
||||
}
|
||||
|
||||
private static function percent(int $numerator, int $denominator): float
|
||||
{
|
||||
if ($denominator <= 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return round(($numerator / $denominator) * 100, 2);
|
||||
}
|
||||
|
||||
private static function safeDivideMoney(float $numerator, int $denominator): float
|
||||
{
|
||||
if ($denominator <= 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return round($numerator / $denominator, 2);
|
||||
}
|
||||
|
||||
private static function safeDivideRatio(float $numerator, float $denominator): float
|
||||
{
|
||||
if ($denominator <= 0) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return round($numerator / $denominator, 2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\validate\stats;
|
||||
|
||||
use app\common\model\stats\PersonalAccountCost;
|
||||
use app\common\validate\BaseValidate;
|
||||
|
||||
class PersonalAccountCostValidate extends BaseValidate
|
||||
{
|
||||
protected $rule = [
|
||||
'id' => 'require|checkExists',
|
||||
'cost_date' => 'require|dateFormat:Y-m-d',
|
||||
'media_source' => 'require|max:120',
|
||||
'amount' => 'require|float|egt:0',
|
||||
'remark' => 'max:255',
|
||||
];
|
||||
|
||||
protected $message = [
|
||||
'id.require' => '参数缺失',
|
||||
'cost_date.require' => '请选择日期',
|
||||
'cost_date.dateFormat' => '日期格式错误',
|
||||
'media_source.require' => '请填写自媒体来源',
|
||||
'media_source.max' => '自媒体来源不能超过120个字符',
|
||||
'amount.require' => '请输入账户消耗金额',
|
||||
'amount.float' => '账户消耗金额格式错误',
|
||||
'amount.egt' => '账户消耗金额不能小于0',
|
||||
'remark.max' => '备注不能超过255个字符',
|
||||
];
|
||||
|
||||
public function sceneAdd()
|
||||
{
|
||||
return $this->remove('id', true);
|
||||
}
|
||||
|
||||
public function sceneEdit()
|
||||
{
|
||||
return $this->remove('cost_date', true)->remove('media_source', true);
|
||||
}
|
||||
|
||||
public function sceneDetail()
|
||||
{
|
||||
return $this->only(['id']);
|
||||
}
|
||||
|
||||
public function sceneDelete()
|
||||
{
|
||||
return $this->only(['id']);
|
||||
}
|
||||
|
||||
public function checkExists($value)
|
||||
{
|
||||
$model = PersonalAccountCost::find($value);
|
||||
if (!$model) {
|
||||
return '记录不存在';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\adminapi\validate\stats;
|
||||
|
||||
use app\common\model\stats\PersonalYeji;
|
||||
use app\common\validate\BaseValidate;
|
||||
|
||||
class PersonalYejiValidate extends BaseValidate
|
||||
{
|
||||
protected $rule = [
|
||||
'id' => 'require|checkExists',
|
||||
'yeji_date' => 'require|dateFormat:Y-m-d',
|
||||
'media_source' => 'require|max:120',
|
||||
'add_fans_count' => 'require|integer|egt:0',
|
||||
'total_open_count' => 'integer|egt:0',
|
||||
'unreplied_count' => 'integer|egt:0',
|
||||
'paid_appointment_count' => 'integer|egt:0',
|
||||
'free_appointment_count' => 'integer|egt:0',
|
||||
'interview_count' => 'integer|egt:0',
|
||||
'order_amount' => 'float|egt:0',
|
||||
'completed_order_count' => 'integer|egt:0',
|
||||
'remark' => 'max:255',
|
||||
];
|
||||
|
||||
protected $message = [
|
||||
'id.require' => '参数缺失',
|
||||
'yeji_date.require' => '请选择业绩日期',
|
||||
'yeji_date.dateFormat' => '业绩日期格式错误',
|
||||
'media_source.require' => '请填写自媒体来源',
|
||||
'media_source.max' => '自媒体来源不能超过120个字符',
|
||||
'add_fans_count.require' => '请填写加粉数',
|
||||
'add_fans_count.integer' => '加粉数格式错误',
|
||||
'add_fans_count.egt' => '加粉数不能小于0',
|
||||
'remark.max' => '备注不能超过255个字符',
|
||||
];
|
||||
|
||||
public function sceneAdd()
|
||||
{
|
||||
return $this->remove('id', true);
|
||||
}
|
||||
|
||||
public function sceneEdit()
|
||||
{
|
||||
return $this->remove('yeji_date', true)->remove('media_source', true);
|
||||
}
|
||||
|
||||
public function sceneDetail()
|
||||
{
|
||||
return $this->only(['id']);
|
||||
}
|
||||
|
||||
public function sceneDelete()
|
||||
{
|
||||
return $this->only(['id']);
|
||||
}
|
||||
|
||||
public function checkExists($value)
|
||||
{
|
||||
$model = PersonalYeji::find($value);
|
||||
if (!$model) {
|
||||
return '记录不存在';
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user