Merge branch 'kpi-statis'
This commit is contained in:
@@ -34,6 +34,20 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="部门" prop="dept_id">
|
||||||
|
<el-tree-select
|
||||||
|
v-model="formData.dept_id"
|
||||||
|
:data="deptOptions"
|
||||||
|
node-key="id"
|
||||||
|
:props="treeProps"
|
||||||
|
:default-expand-all="true"
|
||||||
|
check-strictly
|
||||||
|
placeholder="请选择绑定部门"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
class="w-full"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="账户消耗" prop="amount">
|
<el-form-item label="账户消耗" prop="amount">
|
||||||
<el-input-number
|
<el-input-number
|
||||||
v-model="formData.amount"
|
v-model="formData.amount"
|
||||||
@@ -69,8 +83,15 @@ interface MediaChannelOption {
|
|||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DeptOption {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
children?: DeptOption[]
|
||||||
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
mediaChannelOptions: MediaChannelOption[]
|
mediaChannelOptions: MediaChannelOption[]
|
||||||
|
deptOptions: DeptOption[]
|
||||||
defaultMediaChannelCode: string
|
defaultMediaChannelCode: string
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
@@ -80,11 +101,17 @@ const popupRef = shallowRef<InstanceType<typeof Popup>>()
|
|||||||
const mode = ref<'add' | 'edit'>('add')
|
const mode = ref<'add' | 'edit'>('add')
|
||||||
|
|
||||||
const popupTitle = computed(() => (mode.value === 'edit' ? '编辑账户消耗' : '新增账户消耗'))
|
const popupTitle = computed(() => (mode.value === 'edit' ? '编辑账户消耗' : '新增账户消耗'))
|
||||||
|
const treeProps = {
|
||||||
|
value: 'id',
|
||||||
|
label: 'name',
|
||||||
|
children: 'children',
|
||||||
|
}
|
||||||
|
|
||||||
const formData = reactive({
|
const formData = reactive({
|
||||||
id: '',
|
id: '',
|
||||||
cost_date: '',
|
cost_date: '',
|
||||||
media_channel_code: '',
|
media_channel_code: '',
|
||||||
|
dept_id: '' as string | number,
|
||||||
amount: 0,
|
amount: 0,
|
||||||
remark: '',
|
remark: '',
|
||||||
})
|
})
|
||||||
@@ -104,6 +131,13 @@ const formRules = {
|
|||||||
trigger: ['change'],
|
trigger: ['change'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
dept_id: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请选择绑定部门',
|
||||||
|
trigger: ['change'],
|
||||||
|
},
|
||||||
|
],
|
||||||
amount: [
|
amount: [
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
@@ -117,6 +151,7 @@ const resetForm = () => {
|
|||||||
formData.id = ''
|
formData.id = ''
|
||||||
formData.cost_date = ''
|
formData.cost_date = ''
|
||||||
formData.media_channel_code = props.defaultMediaChannelCode || ''
|
formData.media_channel_code = props.defaultMediaChannelCode || ''
|
||||||
|
formData.dept_id = ''
|
||||||
formData.amount = 0
|
formData.amount = 0
|
||||||
formData.remark = ''
|
formData.remark = ''
|
||||||
}
|
}
|
||||||
@@ -142,6 +177,7 @@ const setFormData = (data: Record<string, any>) => {
|
|||||||
formData.id = data.id ?? ''
|
formData.id = data.id ?? ''
|
||||||
formData.cost_date = data.cost_date ?? ''
|
formData.cost_date = data.cost_date ?? ''
|
||||||
formData.media_channel_code = data.media_channel_code ?? ''
|
formData.media_channel_code = data.media_channel_code ?? ''
|
||||||
|
formData.dept_id = data.dept_id ? Number(data.dept_id) : ''
|
||||||
formData.amount = Number(data.amount ?? 0)
|
formData.amount = Number(data.amount ?? 0)
|
||||||
formData.remark = data.remark ?? ''
|
formData.remark = data.remark ?? ''
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,20 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="部门">
|
||||||
|
<el-tree-select
|
||||||
|
v-model="queryParams.dept_id"
|
||||||
|
:data="deptOptions"
|
||||||
|
node-key="id"
|
||||||
|
:props="treeProps"
|
||||||
|
:default-expand-all="true"
|
||||||
|
check-strictly
|
||||||
|
placeholder="筛选全部部门"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
class="w-[220px]"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
<el-form-item class="w-[280px]" label="备注/维护人">
|
<el-form-item class="w-[280px]" label="备注/维护人">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="queryParams.remark"
|
v-model="queryParams.remark"
|
||||||
@@ -65,6 +79,7 @@
|
|||||||
<el-table class="mt-4" size="large" v-loading="pager.loading" :data="pager.lists">
|
<el-table class="mt-4" size="large" v-loading="pager.loading" :data="pager.lists">
|
||||||
<el-table-column label="日期" prop="cost_date" min-width="120" />
|
<el-table-column label="日期" prop="cost_date" min-width="120" />
|
||||||
<el-table-column label="自媒体渠道" prop="media_channel_name" min-width="120" />
|
<el-table-column label="自媒体渠道" prop="media_channel_name" min-width="120" />
|
||||||
|
<el-table-column label="部门" prop="dept_name" min-width="140" />
|
||||||
<el-table-column label="账户消耗" min-width="120">
|
<el-table-column label="账户消耗" min-width="120">
|
||||||
<template #default="{ row }">¥{{ row.amount }}</template>
|
<template #default="{ row }">¥{{ row.amount }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -103,6 +118,7 @@
|
|||||||
v-if="showEdit"
|
v-if="showEdit"
|
||||||
ref="editRef"
|
ref="editRef"
|
||||||
:media-channel-options="mediaChannelOptions"
|
:media-channel-options="mediaChannelOptions"
|
||||||
|
:dept-options="deptOptions"
|
||||||
:default-media-channel-code="defaultMediaChannelCode"
|
:default-media-channel-code="defaultMediaChannelCode"
|
||||||
@success="getLists"
|
@success="getLists"
|
||||||
@close="showEdit = false"
|
@close="showEdit = false"
|
||||||
@@ -122,6 +138,12 @@ interface MediaChannelOption {
|
|||||||
name: string
|
name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DeptOption {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
children?: DeptOption[]
|
||||||
|
}
|
||||||
|
|
||||||
const editRef = shallowRef<InstanceType<typeof EditPopup>>()
|
const editRef = shallowRef<InstanceType<typeof EditPopup>>()
|
||||||
const showEdit = ref(false)
|
const showEdit = ref(false)
|
||||||
|
|
||||||
@@ -129,6 +151,7 @@ const queryParams = reactive({
|
|||||||
start_date: '',
|
start_date: '',
|
||||||
end_date: '',
|
end_date: '',
|
||||||
media_channel_code: '',
|
media_channel_code: '',
|
||||||
|
dept_id: '',
|
||||||
remark: '',
|
remark: '',
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -147,8 +170,19 @@ const mediaChannelOptions = computed<MediaChannelOption[]>(() => {
|
|||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const deptOptions = computed<DeptOption[]>(() => {
|
||||||
|
const options = pager.extend.dept_options
|
||||||
|
return Array.isArray(options) ? options : []
|
||||||
|
})
|
||||||
|
|
||||||
const defaultMediaChannelCode = computed(() => String(pager.extend.default_media_channel_code || ''))
|
const defaultMediaChannelCode = computed(() => String(pager.extend.default_media_channel_code || ''))
|
||||||
|
|
||||||
|
const treeProps = {
|
||||||
|
value: 'id',
|
||||||
|
label: 'name',
|
||||||
|
children: 'children',
|
||||||
|
}
|
||||||
|
|
||||||
const handleAdd = async () => {
|
const handleAdd = async () => {
|
||||||
showEdit.value = true
|
showEdit.value = true
|
||||||
await nextTick()
|
await nextTick()
|
||||||
@@ -172,6 +206,7 @@ const handleReset = () => {
|
|||||||
queryParams.start_date = ''
|
queryParams.start_date = ''
|
||||||
queryParams.end_date = ''
|
queryParams.end_date = ''
|
||||||
queryParams.media_channel_code = ''
|
queryParams.media_channel_code = ''
|
||||||
|
queryParams.dept_id = ''
|
||||||
queryParams.remark = ''
|
queryParams.remark = ''
|
||||||
resetPage()
|
resetPage()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace app\adminapi\lists\finance;
|
namespace app\adminapi\lists\finance;
|
||||||
|
|
||||||
|
use app\adminapi\logic\dept\DeptLogic;
|
||||||
use app\adminapi\lists\BaseAdminDataLists;
|
use app\adminapi\lists\BaseAdminDataLists;
|
||||||
use app\common\lists\ListsExtendInterface;
|
use app\common\lists\ListsExtendInterface;
|
||||||
use app\common\lists\ListsSearchInterface;
|
use app\common\lists\ListsSearchInterface;
|
||||||
@@ -14,8 +15,13 @@ class AccountCostLists extends BaseAdminDataLists implements ListsSearchInterfac
|
|||||||
{
|
{
|
||||||
public function setSearch(): array
|
public function setSearch(): array
|
||||||
{
|
{
|
||||||
|
$searchFields = ['remark', 'creator_name', 'updater_name'];
|
||||||
|
if (AccountCost::supportsDeptBinding()) {
|
||||||
|
$searchFields[] = 'dept_name';
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'%like%' => ['remark', 'creator_name', 'updater_name'],
|
'%like%' => $searchFields,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,6 +41,10 @@ class AccountCostLists extends BaseAdminDataLists implements ListsSearchInterfac
|
|||||||
$query->where('media_channel_code', trim((string) $this->params['media_channel_code']));
|
$query->where('media_channel_code', trim((string) $this->params['media_channel_code']));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (AccountCost::supportsDeptBinding() && !empty($this->params['dept_id'])) {
|
||||||
|
$query->where('dept_id', (int) $this->params['dept_id']);
|
||||||
|
}
|
||||||
|
|
||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +68,8 @@ class AccountCostLists extends BaseAdminDataLists implements ListsSearchInterfac
|
|||||||
'total_amount' => round((float) $this->baseQuery()->sum('amount'), 2),
|
'total_amount' => round((float) $this->baseQuery()->sum('amount'), 2),
|
||||||
'days_count' => (int) $this->baseQuery()->distinct(true)->count('cost_date'),
|
'days_count' => (int) $this->baseQuery()->distinct(true)->count('cost_date'),
|
||||||
'media_channel_options' => MediaChannelService::getOptions(),
|
'media_channel_options' => MediaChannelService::getOptions(),
|
||||||
|
'dept_options' => DeptLogic::getAllData(),
|
||||||
|
'supports_dept_binding' => AccountCost::supportsDeptBinding(),
|
||||||
'default_media_channel_code' => MediaChannelService::getDefaultCode(),
|
'default_media_channel_code' => MediaChannelService::getDefaultCode(),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace app\adminapi\logic\finance;
|
namespace app\adminapi\logic\finance;
|
||||||
|
|
||||||
use app\common\logic\BaseLogic;
|
use app\common\logic\BaseLogic;
|
||||||
|
use app\common\model\dept\Dept;
|
||||||
use app\common\model\finance\AccountCost;
|
use app\common\model\finance\AccountCost;
|
||||||
use app\common\service\qywx\MediaChannelService;
|
use app\common\service\qywx\MediaChannelService;
|
||||||
|
|
||||||
@@ -16,14 +17,31 @@ class AccountCostLogic extends BaseLogic
|
|||||||
$costDate = (string) $params['cost_date'];
|
$costDate = (string) $params['cost_date'];
|
||||||
$mediaChannelCode = trim((string) ($params['media_channel_code'] ?? ''));
|
$mediaChannelCode = trim((string) ($params['media_channel_code'] ?? ''));
|
||||||
$mediaChannelName = MediaChannelService::getNameByCode($mediaChannelCode);
|
$mediaChannelName = MediaChannelService::getNameByCode($mediaChannelCode);
|
||||||
|
$supportsDeptBinding = AccountCost::supportsDeptBinding();
|
||||||
|
$deptId = $supportsDeptBinding ? (int) ($params['dept_id'] ?? 0) : 0;
|
||||||
|
$deptName = $supportsDeptBinding ? self::resolveDeptName($deptId) : '';
|
||||||
|
|
||||||
if (AccountCost::where('cost_date', $costDate)->where('media_channel_code', $mediaChannelCode)->count() > 0) {
|
if ($supportsDeptBinding && $deptName === '') {
|
||||||
self::setError('该日期下所选渠道的账户消耗已存在,请直接编辑');
|
self::setError('请选择绑定部门');
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
AccountCost::create([
|
$existsQuery = AccountCost::where('cost_date', $costDate)
|
||||||
|
->where('media_channel_code', $mediaChannelCode);
|
||||||
|
if ($supportsDeptBinding) {
|
||||||
|
$existsQuery->where('dept_id', $deptId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($existsQuery->count() > 0) {
|
||||||
|
self::setError($supportsDeptBinding
|
||||||
|
? '该日期下所选渠道和部门的账户消耗已存在,请直接编辑'
|
||||||
|
: '该日期下所选渠道的账户消耗已存在,请直接编辑');
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload = [
|
||||||
'cost_date' => $costDate,
|
'cost_date' => $costDate,
|
||||||
'media_channel_code' => $mediaChannelCode,
|
'media_channel_code' => $mediaChannelCode,
|
||||||
'media_channel_name' => $mediaChannelName,
|
'media_channel_name' => $mediaChannelName,
|
||||||
@@ -33,7 +51,13 @@ class AccountCostLogic extends BaseLogic
|
|||||||
'creator_name' => $adminName,
|
'creator_name' => $adminName,
|
||||||
'updater_id' => $adminId,
|
'updater_id' => $adminId,
|
||||||
'updater_name' => $adminName,
|
'updater_name' => $adminName,
|
||||||
]);
|
];
|
||||||
|
if ($supportsDeptBinding) {
|
||||||
|
$payload['dept_id'] = $deptId;
|
||||||
|
$payload['dept_name'] = $deptName;
|
||||||
|
}
|
||||||
|
|
||||||
|
AccountCost::create($payload);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
@@ -53,6 +77,31 @@ class AccountCostLogic extends BaseLogic
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$supportsDeptBinding = AccountCost::supportsDeptBinding();
|
||||||
|
$deptId = $supportsDeptBinding ? (int) ($params['dept_id'] ?? 0) : 0;
|
||||||
|
$deptName = $supportsDeptBinding ? self::resolveDeptName($deptId) : '';
|
||||||
|
if ($supportsDeptBinding && $deptName === '') {
|
||||||
|
self::setError('请选择绑定部门');
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($supportsDeptBinding) {
|
||||||
|
if (AccountCost::where('id', '<>', (int) $params['id'])
|
||||||
|
->where('cost_date', (string) $model->cost_date)
|
||||||
|
->where('media_channel_code', (string) $model->media_channel_code)
|
||||||
|
->where('dept_id', $deptId)
|
||||||
|
->count() > 0) {
|
||||||
|
self::setError('该日期下所选渠道和部门的账户消耗已存在,请直接编辑');
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($supportsDeptBinding) {
|
||||||
|
$model->dept_id = $deptId;
|
||||||
|
$model->dept_name = $deptName;
|
||||||
|
}
|
||||||
$model->amount = round((float) $params['amount'], 2);
|
$model->amount = round((float) $params['amount'], 2);
|
||||||
$model->remark = (string) ($params['remark'] ?? '');
|
$model->remark = (string) ($params['remark'] ?? '');
|
||||||
$model->updater_id = $adminId;
|
$model->updater_id = $adminId;
|
||||||
@@ -91,4 +140,18 @@ class AccountCostLogic extends BaseLogic
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function resolveDeptName(int $deptId): string
|
||||||
|
{
|
||||||
|
if ($deptId <= 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$dept = Dept::find($deptId);
|
||||||
|
if (!$dept) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) ($dept->name ?? '');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace app\adminapi\logic\stats;
|
|||||||
|
|
||||||
use app\adminapi\logic\dept\DeptLogic;
|
use app\adminapi\logic\dept\DeptLogic;
|
||||||
use app\adminapi\logic\tcm\DiagnosisLogic;
|
use app\adminapi\logic\tcm\DiagnosisLogic;
|
||||||
|
use app\common\model\finance\AccountCost;
|
||||||
use app\common\service\qywx\MediaChannelService;
|
use app\common\service\qywx\MediaChannelService;
|
||||||
use think\facade\Db;
|
use think\facade\Db;
|
||||||
|
|
||||||
@@ -28,10 +29,9 @@ class ConversionLogic
|
|||||||
$pageNo = max(1, (int)($params['page_no'] ?? 1));
|
$pageNo = max(1, (int)($params['page_no'] ?? 1));
|
||||||
$pageSize = max(1, min(100, (int)($params['page_size'] ?? 15)));
|
$pageSize = max(1, min(100, (int)($params['page_size'] ?? 15)));
|
||||||
|
|
||||||
$allocationParams = self::buildAllocationScopeParams($dimension, $params);
|
|
||||||
$entities = self::loadEntities($dimension, $params);
|
$entities = self::loadEntities($dimension, $params);
|
||||||
$entityIds = array_keys($entities);
|
$entityIds = array_keys($entities);
|
||||||
$allocationEntities = self::loadEntities($dimension, $allocationParams);
|
$allocationEntities = self::loadEntities($dimension, $params);
|
||||||
$allocationEntityIds = array_keys($allocationEntities);
|
$allocationEntityIds = array_keys($allocationEntities);
|
||||||
|
|
||||||
if ($entityIds === []) {
|
if ($entityIds === []) {
|
||||||
@@ -60,11 +60,54 @@ class ConversionLogic
|
|||||||
|
|
||||||
$adminToDeptIds = self::loadAdminDeptMap();
|
$adminToDeptIds = self::loadAdminDeptMap();
|
||||||
self::hydrateFanStats($allocationEntities, $dimension, $allocationEntityIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $mediaChannel);
|
self::hydrateFanStats($allocationEntities, $dimension, $allocationEntityIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $mediaChannel);
|
||||||
$allocationAddFansCount = self::sumEntityAddFans(array_values($allocationEntities));
|
|
||||||
self::hydrateFanStats($entities, $dimension, $entityIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $mediaChannel);
|
self::hydrateFanStats($entities, $dimension, $entityIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $mediaChannel);
|
||||||
self::hydrateAppointmentStats($entities, $dimension, $entityIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $startDate, $endDate, $mediaChannel);
|
self::hydrateAppointmentStats($entities, $dimension, $entityIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $startDate, $endDate, $mediaChannel);
|
||||||
self::hydrateOrderAndAmountStats($entities, $dimension, $entityIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $mediaChannel);
|
self::hydrateOrderAndAmountStats($entities, $dimension, $entityIds, $adminToDeptIds, $startTimestamp, $endTimestamp, $mediaChannel);
|
||||||
$globalAccountCost = self::hydrateAccountCostStats($entities, $startDate, $endDate, $mediaChannelCode);
|
[$globalAccountCost, $accountCostDeptIds] = self::hydrateAccountCostStats($entities, $startDate, $endDate, $mediaChannelCode);
|
||||||
|
$supportsDeptBinding = AccountCost::supportsDeptBinding();
|
||||||
|
$restrictAccountCostByDept = $supportsDeptBinding;
|
||||||
|
$restrictStatsByDept = $supportsDeptBinding && $mediaChannelCode !== '';
|
||||||
|
$eligibleDeptIds = $restrictAccountCostByDept ? self::expandDeptIdsWithDescendants($accountCostDeptIds) : $accountCostDeptIds;
|
||||||
|
|
||||||
|
if ($restrictStatsByDept) {
|
||||||
|
$entities = self::filterEntitiesByChannelDeptScope($entities, $dimension, $eligibleDeptIds, $adminToDeptIds);
|
||||||
|
$allocationEntities = self::filterEntitiesByChannelDeptScope($allocationEntities, $dimension, $eligibleDeptIds, $adminToDeptIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
$entityIds = array_keys($entities);
|
||||||
|
$allocationEntityIds = array_keys($allocationEntities);
|
||||||
|
|
||||||
|
if ($entityIds === []) {
|
||||||
|
$result = [
|
||||||
|
'dimension' => $dimension,
|
||||||
|
'date_range' => [$startDate, $endDate],
|
||||||
|
'summary' => self::buildSummary([]),
|
||||||
|
'charts' => self::buildCharts([]),
|
||||||
|
'lists' => [],
|
||||||
|
'count' => 0,
|
||||||
|
'page_no' => $pageNo,
|
||||||
|
'page_size' => $pageSize,
|
||||||
|
'extend' => [
|
||||||
|
'dimension' => $dimension,
|
||||||
|
'date_range' => [$startDate, $endDate],
|
||||||
|
'summary' => self::buildSummary([]),
|
||||||
|
'charts' => self::buildCharts([]),
|
||||||
|
],
|
||||||
|
];
|
||||||
|
if ($includeFilters) {
|
||||||
|
$result['extend']['filters'] = self::buildFilterOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
$allocationAddFansCount = self::sumEntityAddFansForAccountCost(
|
||||||
|
array_values($allocationEntities),
|
||||||
|
$dimension,
|
||||||
|
$restrictAccountCostByDept,
|
||||||
|
$eligibleDeptIds,
|
||||||
|
$adminToDeptIds
|
||||||
|
);
|
||||||
|
|
||||||
if ($dimension === 'dept') {
|
if ($dimension === 'dept') {
|
||||||
[$allRows, $pagedRows, $chartRows] = self::buildDeptTreeRows(
|
[$allRows, $pagedRows, $chartRows] = self::buildDeptTreeRows(
|
||||||
@@ -73,7 +116,10 @@ class ConversionLogic
|
|||||||
$pageNo,
|
$pageNo,
|
||||||
$pageSize,
|
$pageSize,
|
||||||
$filterEmptyEntities,
|
$filterEmptyEntities,
|
||||||
$allocationAddFansCount
|
$allocationAddFansCount,
|
||||||
|
$restrictAccountCostByDept,
|
||||||
|
$eligibleDeptIds,
|
||||||
|
$adminToDeptIds
|
||||||
);
|
);
|
||||||
|
|
||||||
$result = [
|
$result = [
|
||||||
@@ -99,7 +145,15 @@ class ConversionLogic
|
|||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
$rows = self::finalizeRows($entities, $filterEmptyEntities, $allocationAddFansCount);
|
$rows = self::finalizeRows(
|
||||||
|
$entities,
|
||||||
|
$filterEmptyEntities,
|
||||||
|
$allocationAddFansCount,
|
||||||
|
$restrictAccountCostByDept,
|
||||||
|
$eligibleDeptIds,
|
||||||
|
$adminToDeptIds,
|
||||||
|
$dimension
|
||||||
|
);
|
||||||
$count = count($rows);
|
$count = count($rows);
|
||||||
$offset = ($pageNo - 1) * $pageSize;
|
$offset = ($pageNo - 1) * $pageSize;
|
||||||
|
|
||||||
@@ -126,15 +180,6 @@ class ConversionLogic
|
|||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function buildAllocationScopeParams(string $dimension, array $params): array
|
|
||||||
{
|
|
||||||
$allocationParams = $params;
|
|
||||||
|
|
||||||
unset($allocationParams['dept_id'], $allocationParams['assistant_id'], $allocationParams['doctor_id']);
|
|
||||||
|
|
||||||
return $allocationParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
@@ -373,6 +418,67 @@ class ConversionLogic
|
|||||||
return $map;
|
return $map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int[] $deptIds
|
||||||
|
* @return int[]
|
||||||
|
*/
|
||||||
|
private static function expandDeptIdsWithDescendants(array $deptIds): array
|
||||||
|
{
|
||||||
|
if ($deptIds === []) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$expanded = [];
|
||||||
|
foreach (array_values(array_unique(array_filter(array_map('intval', $deptIds), static fn (int $deptId): bool => $deptId > 0))) as $deptId) {
|
||||||
|
foreach (DeptLogic::getSelfAndDescendantIds($deptId) as $expandedDeptId) {
|
||||||
|
if ($expandedDeptId > 0) {
|
||||||
|
$expanded[$expandedDeptId] = $expandedDeptId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values($expanded);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自媒体渠道选中时,统计范围只允许落在渠道绑定部门及其子部门内。
|
||||||
|
*
|
||||||
|
* @param array<int, array<string, mixed>> $entities
|
||||||
|
* @param array<int, int[]> $adminToDeptIds
|
||||||
|
* @return array<int, array<string, mixed>>
|
||||||
|
*/
|
||||||
|
private static function filterEntitiesByChannelDeptScope(array $entities, string $dimension, array $eligibleDeptIds, array $adminToDeptIds): array
|
||||||
|
{
|
||||||
|
if ($eligibleDeptIds === []) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$eligibleDeptMap = array_fill_keys(array_map('intval', $eligibleDeptIds), true);
|
||||||
|
$filtered = [];
|
||||||
|
foreach ($entities as $key => $entity) {
|
||||||
|
$entityId = (int)($entity['id'] ?? 0);
|
||||||
|
if ($entityId <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dimension === 'dept') {
|
||||||
|
if (isset($eligibleDeptMap[$entityId])) {
|
||||||
|
$filtered[$key] = $entity;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (($adminToDeptIds[$entityId] ?? []) as $deptId) {
|
||||||
|
if (isset($eligibleDeptMap[(int)$deptId])) {
|
||||||
|
$filtered[$key] = $entity;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $filtered;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<int, array<string, mixed>> $entities
|
* @param array<int, array<string, mixed>> $entities
|
||||||
* @param int[] $entityIds
|
* @param int[] $entityIds
|
||||||
@@ -639,22 +745,43 @@ class ConversionLogic
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 账户消耗:来源于独立维护表 zyt_account_cost,为全局日维度数据。
|
* 账户消耗:来源于独立维护表 zyt_account_cost。
|
||||||
* 由于当前没有部门/医助/医生拆分来源,只在当前筛选口径的汇总层使用,不向各明细行分摊。
|
|
||||||
*
|
*
|
||||||
* @param array<int, array<string, mixed>> $entities
|
* @param array<int, array<string, mixed>> $entities
|
||||||
|
* @return array{0: float, 1: int[]}
|
||||||
*/
|
*/
|
||||||
private static function hydrateAccountCostStats(array &$entities, string $startDate, string $endDate, string $mediaChannelCode): float
|
private static function hydrateAccountCostStats(array &$entities, string $startDate, string $endDate, string $mediaChannelCode): array
|
||||||
{
|
{
|
||||||
|
$supportsDeptBinding = AccountCost::supportsDeptBinding();
|
||||||
$query = Db::name('account_cost')
|
$query = Db::name('account_cost')
|
||||||
->where('cost_date', '>=', $startDate)
|
->where('cost_date', '>=', $startDate)
|
||||||
->where('cost_date', '<=', $endDate);
|
->where('cost_date', '<=', $endDate);
|
||||||
|
|
||||||
|
if ($supportsDeptBinding) {
|
||||||
|
$query->field('dept_id, amount');
|
||||||
|
} else {
|
||||||
|
$query->field('amount');
|
||||||
|
}
|
||||||
|
|
||||||
if ($mediaChannelCode !== '') {
|
if ($mediaChannelCode !== '') {
|
||||||
$query->where('media_channel_code', $mediaChannelCode);
|
$query->where('media_channel_code', $mediaChannelCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
$totalAmount = round((float) $query->sum('amount'), 2);
|
if ($supportsDeptBinding) {
|
||||||
|
$query->where('dept_id', '>', 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
$rows = $query->select()->toArray();
|
||||||
|
$totalAmount = 0.0;
|
||||||
|
$deptIds = [];
|
||||||
|
foreach ($rows as $row) {
|
||||||
|
$amount = round((float)($row['amount'] ?? 0), 2);
|
||||||
|
$totalAmount = round($totalAmount + $amount, 2);
|
||||||
|
$deptId = $supportsDeptBinding ? (int)($row['dept_id'] ?? 0) : 0;
|
||||||
|
if ($supportsDeptBinding && $deptId > 0) {
|
||||||
|
$deptIds[$deptId] = $deptId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($entities as &$entity) {
|
foreach ($entities as &$entity) {
|
||||||
$entity['account_cost'] = 0.0;
|
$entity['account_cost'] = 0.0;
|
||||||
@@ -662,7 +789,7 @@ class ConversionLogic
|
|||||||
}
|
}
|
||||||
unset($entity);
|
unset($entity);
|
||||||
|
|
||||||
return $totalAmount;
|
return [$totalAmount, array_values($deptIds)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -694,7 +821,15 @@ class ConversionLogic
|
|||||||
* @param array<int, array<string, mixed>> $entities
|
* @param array<int, array<string, mixed>> $entities
|
||||||
* @return array<int, array<string, mixed>>
|
* @return array<int, array<string, mixed>>
|
||||||
*/
|
*/
|
||||||
private static function finalizeRows(array $entities, bool $excludeEmpty = false, ?int $allocationAddFansCount = null): array
|
private static function finalizeRows(
|
||||||
|
array $entities,
|
||||||
|
bool $excludeEmpty = false,
|
||||||
|
?int $allocationAddFansCount = null,
|
||||||
|
bool $restrictAccountCostByDept = false,
|
||||||
|
array $eligibleDeptIds = [],
|
||||||
|
array $adminToDeptIds = [],
|
||||||
|
string $dimension = 'dept'
|
||||||
|
): array
|
||||||
{
|
{
|
||||||
$rows = [];
|
$rows = [];
|
||||||
$filteredEntities = $excludeEmpty
|
$filteredEntities = $excludeEmpty
|
||||||
@@ -714,7 +849,8 @@ class ConversionLogic
|
|||||||
$completedOrderCount = (int)$entity['completed_order_count'];
|
$completedOrderCount = (int)$entity['completed_order_count'];
|
||||||
$businessOrderAmount = round((float)$entity['business_order_amount'], 2);
|
$businessOrderAmount = round((float)$entity['business_order_amount'], 2);
|
||||||
$completedOrderAmount = round((float)$entity['completed_order_amount'], 2);
|
$completedOrderAmount = round((float)$entity['completed_order_amount'], 2);
|
||||||
$effectiveAccountCost = self::allocateAccountCost($globalAccountCost, $globalAddFansCount, $addFansCount);
|
$isEligibleForAccountCost = self::isAccountCostEligible($dimension, $entity, $restrictAccountCostByDept, $eligibleDeptIds, $adminToDeptIds);
|
||||||
|
$effectiveAccountCost = self::allocateAccountCost($globalAccountCost, $globalAddFansCount, $addFansCount, $isEligibleForAccountCost);
|
||||||
|
|
||||||
$entity['paid_appointment_count'] = $paidAppointmentCount;
|
$entity['paid_appointment_count'] = $paidAppointmentCount;
|
||||||
$entity['free_appointment_count'] = $freeAppointmentCount;
|
$entity['free_appointment_count'] = $freeAppointmentCount;
|
||||||
@@ -758,7 +894,17 @@ class ConversionLogic
|
|||||||
* @param array<int, array<string, mixed>> $entities
|
* @param array<int, array<string, mixed>> $entities
|
||||||
* @return array{0: array<int, array<string, mixed>>, 1: array<int, array<string, mixed>>, 2: array<int, array<string, mixed>>}
|
* @return array{0: array<int, array<string, mixed>>, 1: array<int, array<string, mixed>>, 2: array<int, array<string, mixed>>}
|
||||||
*/
|
*/
|
||||||
private static function buildDeptTreeRows(array $entities, int $selectedDeptId, int $pageNo, int $pageSize, bool $excludeEmpty = false, ?int $allocationAddFansCount = null): array
|
private static function buildDeptTreeRows(
|
||||||
|
array $entities,
|
||||||
|
int $selectedDeptId,
|
||||||
|
int $pageNo,
|
||||||
|
int $pageSize,
|
||||||
|
bool $excludeEmpty = false,
|
||||||
|
?int $allocationAddFansCount = null,
|
||||||
|
bool $restrictAccountCostByDept = false,
|
||||||
|
array $eligibleDeptIds = [],
|
||||||
|
array $adminToDeptIds = []
|
||||||
|
): array
|
||||||
{
|
{
|
||||||
$childrenByPid = [];
|
$childrenByPid = [];
|
||||||
foreach ($entities as $entity) {
|
foreach ($entities as $entity) {
|
||||||
@@ -845,11 +991,11 @@ class ConversionLogic
|
|||||||
$globalAccountCost = self::extractGlobalAccountCost($allRowsRaw);
|
$globalAccountCost = self::extractGlobalAccountCost($allRowsRaw);
|
||||||
$globalAddFansCount = $allocationAddFansCount ?? self::sumNodeAddFans($allRowsRaw);
|
$globalAddFansCount = $allocationAddFansCount ?? self::sumNodeAddFans($allRowsRaw);
|
||||||
$allRows = array_map(
|
$allRows = array_map(
|
||||||
static fn (array $row): array => self::finalizeDeptNode($row, $globalAccountCost, $globalAddFansCount),
|
static fn (array $row): array => self::finalizeDeptNode($row, $globalAccountCost, $globalAddFansCount, $restrictAccountCostByDept, $eligibleDeptIds, $adminToDeptIds),
|
||||||
$allRowsRaw
|
$allRowsRaw
|
||||||
);
|
);
|
||||||
$chartRows = array_map(
|
$chartRows = array_map(
|
||||||
static fn (array $row): array => self::finalizeDeptNode($row, $globalAccountCost, $globalAddFansCount),
|
static fn (array $row): array => self::finalizeDeptNode($row, $globalAccountCost, $globalAddFansCount, $restrictAccountCostByDept, $eligibleDeptIds, $adminToDeptIds),
|
||||||
$chartRowsRaw
|
$chartRowsRaw
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -910,10 +1056,17 @@ class ConversionLogic
|
|||||||
* @param array<string, mixed> $node
|
* @param array<string, mixed> $node
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
private static function finalizeDeptNode(array $node, float $globalAccountCost, int $globalAddFansCount): array
|
private static function finalizeDeptNode(
|
||||||
|
array $node,
|
||||||
|
float $globalAccountCost,
|
||||||
|
int $globalAddFansCount,
|
||||||
|
bool $restrictAccountCostByDept = false,
|
||||||
|
array $eligibleDeptIds = [],
|
||||||
|
array $adminToDeptIds = []
|
||||||
|
): array
|
||||||
{
|
{
|
||||||
$node['children'] = array_map(
|
$node['children'] = array_map(
|
||||||
static fn (array $child): array => self::finalizeDeptNode($child, $globalAccountCost, $globalAddFansCount),
|
static fn (array $child): array => self::finalizeDeptNode($child, $globalAccountCost, $globalAddFansCount, $restrictAccountCostByDept, $eligibleDeptIds, $adminToDeptIds),
|
||||||
$node['children'] ?? []
|
$node['children'] ?? []
|
||||||
);
|
);
|
||||||
$paidAppointmentCount = (int)($node['paid_appointment_count'] ?? 0);
|
$paidAppointmentCount = (int)($node['paid_appointment_count'] ?? 0);
|
||||||
@@ -925,14 +1078,19 @@ class ConversionLogic
|
|||||||
$completedOrderCount = (int)$node['completed_order_count'];
|
$completedOrderCount = (int)$node['completed_order_count'];
|
||||||
$businessOrderAmount = round((float)$node['business_order_amount'], 2);
|
$businessOrderAmount = round((float)$node['business_order_amount'], 2);
|
||||||
$completedOrderAmount = round((float)$node['completed_order_amount'], 2);
|
$completedOrderAmount = round((float)$node['completed_order_amount'], 2);
|
||||||
$effectiveAccountCost = self::allocateAccountCost($globalAccountCost, $globalAddFansCount, $addFansCount);
|
$isEligibleForAccountCost = self::isDeptAccountCostEligible((int) ($node['id'] ?? 0), $restrictAccountCostByDept, $eligibleDeptIds);
|
||||||
|
$effectiveAccountCost = self::allocateAccountCost($globalAccountCost, $globalAddFansCount, $addFansCount, $isEligibleForAccountCost);
|
||||||
|
$childrenAccountCost = 0.0;
|
||||||
|
foreach ($node['children'] as $child) {
|
||||||
|
$childrenAccountCost = round($childrenAccountCost + (float)($child['account_cost'] ?? 0), 2);
|
||||||
|
}
|
||||||
|
|
||||||
$node['paid_appointment_count'] = $paidAppointmentCount;
|
$node['paid_appointment_count'] = $paidAppointmentCount;
|
||||||
$node['free_appointment_count'] = $freeAppointmentCount;
|
$node['free_appointment_count'] = $freeAppointmentCount;
|
||||||
$node['appointment_total_count'] = $appointmentTotalCount;
|
$node['appointment_total_count'] = $appointmentTotalCount;
|
||||||
$node['business_order_amount'] = $businessOrderAmount;
|
$node['business_order_amount'] = $businessOrderAmount;
|
||||||
$node['completed_order_amount'] = $completedOrderAmount;
|
$node['completed_order_amount'] = $completedOrderAmount;
|
||||||
$node['account_cost'] = $effectiveAccountCost;
|
$node['account_cost'] = $isEligibleForAccountCost ? $effectiveAccountCost : $childrenAccountCost;
|
||||||
$node['paid_appointment_rate'] = self::percent($paidAppointmentCount, $addFansCount);
|
$node['paid_appointment_rate'] = self::percent($paidAppointmentCount, $addFansCount);
|
||||||
$node['open_appointment_rate'] = self::percent($paidAppointmentCount, $totalOpenCount);
|
$node['open_appointment_rate'] = self::percent($paidAppointmentCount, $totalOpenCount);
|
||||||
$node['interview_rate'] = self::percent($interviewCount, $appointmentTotalCount);
|
$node['interview_rate'] = self::percent($interviewCount, $appointmentTotalCount);
|
||||||
@@ -1035,9 +1193,61 @@ class ConversionLogic
|
|||||||
return $total;
|
return $total;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function allocateAccountCost(float $globalAccountCost, int $globalAddFansCount, int $rowAddFansCount): float
|
private static function isAccountCostEligible(string $dimension, array $entity, bool $restrictAccountCostByDept, array $eligibleDeptIds, array $adminToDeptIds): bool
|
||||||
{
|
{
|
||||||
if ($globalAccountCost <= 0 || $globalAddFansCount <= 0 || $rowAddFansCount <= 0) {
|
if (!$restrictAccountCostByDept) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dimension === 'dept') {
|
||||||
|
return self::isDeptAccountCostEligible((int) ($entity['id'] ?? 0), true, $eligibleDeptIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
$adminId = (int) ($entity['id'] ?? 0);
|
||||||
|
$deptIds = $adminToDeptIds[$adminId] ?? [];
|
||||||
|
foreach ($deptIds as $deptId) {
|
||||||
|
if (in_array((int) $deptId, $eligibleDeptIds, true)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function isDeptAccountCostEligible(int $deptId, bool $restrictAccountCostByDept, array $eligibleDeptIds): bool
|
||||||
|
{
|
||||||
|
if (!$restrictAccountCostByDept) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($deptId <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return in_array($deptId, $eligibleDeptIds, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function sumEntityAddFansForAccountCost(array $entities, string $dimension, bool $restrictAccountCostByDept, array $eligibleDeptIds, array $adminToDeptIds): int
|
||||||
|
{
|
||||||
|
if (!$restrictAccountCostByDept) {
|
||||||
|
return self::sumEntityAddFans($entities);
|
||||||
|
}
|
||||||
|
|
||||||
|
$total = 0;
|
||||||
|
foreach ($entities as $entity) {
|
||||||
|
if (!self::isAccountCostEligible($dimension, $entity, true, $eligibleDeptIds, $adminToDeptIds)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$total += (int) ($entity['add_fans_count'] ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $total;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function allocateAccountCost(float $globalAccountCost, int $globalAddFansCount, int $rowAddFansCount, bool $isEligible = true): float
|
||||||
|
{
|
||||||
|
if (!$isEligible || $globalAccountCost <= 0 || $globalAddFansCount <= 0 || $rowAddFansCount <= 0) {
|
||||||
return 0.0;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace app\adminapi\validate\finance;
|
namespace app\adminapi\validate\finance;
|
||||||
|
|
||||||
|
use app\common\model\dept\Dept;
|
||||||
use app\common\model\finance\AccountCost;
|
use app\common\model\finance\AccountCost;
|
||||||
use app\common\service\qywx\MediaChannelService;
|
use app\common\service\qywx\MediaChannelService;
|
||||||
use app\common\validate\BaseValidate;
|
use app\common\validate\BaseValidate;
|
||||||
@@ -14,6 +15,7 @@ class AccountCostValidate extends BaseValidate
|
|||||||
'id' => 'require|checkExists',
|
'id' => 'require|checkExists',
|
||||||
'cost_date' => 'require|dateFormat:Y-m-d',
|
'cost_date' => 'require|dateFormat:Y-m-d',
|
||||||
'media_channel_code' => 'require|checkMediaChannelCode',
|
'media_channel_code' => 'require|checkMediaChannelCode',
|
||||||
|
'dept_id' => 'require|checkDeptId',
|
||||||
'amount' => 'require|float|egt:0',
|
'amount' => 'require|float|egt:0',
|
||||||
'remark' => 'max:255',
|
'remark' => 'max:255',
|
||||||
];
|
];
|
||||||
@@ -23,6 +25,7 @@ class AccountCostValidate extends BaseValidate
|
|||||||
'cost_date.require' => '请选择日期',
|
'cost_date.require' => '请选择日期',
|
||||||
'cost_date.dateFormat' => '日期格式错误',
|
'cost_date.dateFormat' => '日期格式错误',
|
||||||
'media_channel_code.require' => '请选择自媒体渠道',
|
'media_channel_code.require' => '请选择自媒体渠道',
|
||||||
|
'dept_id.require' => '请选择绑定部门',
|
||||||
'amount.require' => '请输入账户消耗金额',
|
'amount.require' => '请输入账户消耗金额',
|
||||||
'amount.float' => '账户消耗金额格式错误',
|
'amount.float' => '账户消耗金额格式错误',
|
||||||
'amount.egt' => '账户消耗金额不能小于0',
|
'amount.egt' => '账户消耗金额不能小于0',
|
||||||
@@ -31,11 +34,19 @@ class AccountCostValidate extends BaseValidate
|
|||||||
|
|
||||||
public function sceneAdd()
|
public function sceneAdd()
|
||||||
{
|
{
|
||||||
|
if (!AccountCost::supportsDeptBinding()) {
|
||||||
|
return $this->remove('id', true)->remove('dept_id', true);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->remove('id', true);
|
return $this->remove('id', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function sceneEdit()
|
public function sceneEdit()
|
||||||
{
|
{
|
||||||
|
if (!AccountCost::supportsDeptBinding()) {
|
||||||
|
return $this->remove('cost_date', true)->remove('dept_id', true);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->remove('cost_date', true);
|
return $this->remove('cost_date', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,4 +78,22 @@ class AccountCostValidate extends BaseValidate
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function checkDeptId($value)
|
||||||
|
{
|
||||||
|
if (!AccountCost::supportsDeptBinding()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$deptId = (int) $value;
|
||||||
|
if ($deptId <= 0) {
|
||||||
|
return '请选择绑定部门';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Dept::find($deptId)) {
|
||||||
|
return '绑定部门不存在';
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace app\common\model\finance;
|
namespace app\common\model\finance;
|
||||||
|
|
||||||
use app\common\model\BaseModel;
|
use app\common\model\BaseModel;
|
||||||
|
use think\facade\Db;
|
||||||
|
|
||||||
class AccountCost extends BaseModel
|
class AccountCost extends BaseModel
|
||||||
{
|
{
|
||||||
@@ -17,4 +18,23 @@ class AccountCost extends BaseModel
|
|||||||
protected $updateTime = 'update_time';
|
protected $updateTime = 'update_time';
|
||||||
|
|
||||||
protected $dateFormat = 'Y-m-d H:i:s';
|
protected $dateFormat = 'Y-m-d H:i:s';
|
||||||
|
|
||||||
|
public static function supportsDeptBinding(): bool
|
||||||
|
{
|
||||||
|
static $supported = null;
|
||||||
|
if ($supported !== null) {
|
||||||
|
return $supported;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$fields = Db::name('account_cost')->getTableFields();
|
||||||
|
$supported = is_array($fields)
|
||||||
|
&& in_array('dept_id', $fields, true)
|
||||||
|
&& in_array('dept_name', $fields, true);
|
||||||
|
} catch (\Throwable) {
|
||||||
|
$supported = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $supported;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-3
@@ -19,9 +19,11 @@ CREATE TABLE IF NOT EXISTS `zyt_qywx_media_channel` (
|
|||||||
|
|
||||||
ALTER TABLE `zyt_account_cost`
|
ALTER TABLE `zyt_account_cost`
|
||||||
ADD COLUMN `media_channel_code` varchar(80) NOT NULL DEFAULT '' COMMENT '渠道编码' AFTER `cost_date`,
|
ADD COLUMN `media_channel_code` varchar(80) NOT NULL DEFAULT '' COMMENT '渠道编码' AFTER `cost_date`,
|
||||||
ADD COLUMN `media_channel_name` varchar(100) NOT NULL DEFAULT '' COMMENT '渠道名称' AFTER `media_channel_code`;
|
ADD COLUMN `media_channel_name` varchar(100) NOT NULL DEFAULT '' COMMENT '渠道名称' AFTER `media_channel_code`,
|
||||||
|
ADD COLUMN `dept_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '绑定部门ID' AFTER `media_channel_name`,
|
||||||
|
ADD COLUMN `dept_name` varchar(100) NOT NULL DEFAULT '' COMMENT '绑定部门名称' AFTER `dept_id`;
|
||||||
|
|
||||||
ALTER TABLE `zyt_account_cost`
|
ALTER TABLE `zyt_account_cost`
|
||||||
DROP INDEX `uk_cost_date`,
|
DROP INDEX `uk_cost_date`,
|
||||||
ADD UNIQUE KEY `uk_cost_date_channel` (`cost_date`, `media_channel_code`),
|
ADD UNIQUE KEY `uk_cost_date_channel_dept` (`cost_date`, `media_channel_code`, `dept_id`),
|
||||||
ADD KEY `idx_cost_date_channel` (`cost_date`, `media_channel_code`);
|
ADD KEY `idx_cost_date_channel_dept` (`cost_date`, `media_channel_code`, `dept_id`);
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ CREATE TABLE IF NOT EXISTS `zyt_account_cost` (
|
|||||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||||
`cost_date` date NOT NULL COMMENT '账户消耗日期',
|
`cost_date` date NOT NULL COMMENT '账户消耗日期',
|
||||||
`amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '账户消耗金额',
|
`amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '账户消耗金额',
|
||||||
|
`dept_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '绑定部门ID',
|
||||||
|
`dept_name` varchar(100) NOT NULL DEFAULT '' COMMENT '绑定部门名称',
|
||||||
|
`media_channel_code` varchar(80) NOT NULL DEFAULT '' COMMENT '渠道编码',
|
||||||
|
`media_channel_name` varchar(100) NOT NULL DEFAULT '' COMMENT '渠道名称',
|
||||||
`remark` varchar(255) NOT NULL DEFAULT '' COMMENT '备注',
|
`remark` varchar(255) NOT NULL DEFAULT '' COMMENT '备注',
|
||||||
`creator_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建人ID',
|
`creator_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建人ID',
|
||||||
`creator_name` varchar(64) NOT NULL DEFAULT '' COMMENT '创建人姓名',
|
`creator_name` varchar(64) NOT NULL DEFAULT '' COMMENT '创建人姓名',
|
||||||
@@ -10,6 +14,7 @@ CREATE TABLE IF NOT EXISTS `zyt_account_cost` (
|
|||||||
`create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
|
`create_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建时间',
|
||||||
`update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
|
`update_time` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新时间',
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
UNIQUE KEY `uk_cost_date` (`cost_date`),
|
UNIQUE KEY `uk_cost_date_channel_dept` (`cost_date`, `media_channel_code`, `dept_id`),
|
||||||
|
KEY `idx_cost_date_channel_dept` (`cost_date`, `media_channel_code`, `dept_id`),
|
||||||
KEY `idx_update_time` (`update_time`)
|
KEY `idx_update_time` (`update_time`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='账户消耗表';
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='账户消耗表';
|
||||||
|
|||||||
@@ -19,12 +19,14 @@ CREATE TABLE IF NOT EXISTS `zyt_qywx_media_channel` (
|
|||||||
|
|
||||||
ALTER TABLE `zyt_account_cost`
|
ALTER TABLE `zyt_account_cost`
|
||||||
ADD COLUMN `media_channel_code` varchar(80) NOT NULL DEFAULT '' COMMENT '渠道编码' AFTER `cost_date`,
|
ADD COLUMN `media_channel_code` varchar(80) NOT NULL DEFAULT '' COMMENT '渠道编码' AFTER `cost_date`,
|
||||||
ADD COLUMN `media_channel_name` varchar(100) NOT NULL DEFAULT '' COMMENT '渠道名称' AFTER `media_channel_code`;
|
ADD COLUMN `media_channel_name` varchar(100) NOT NULL DEFAULT '' COMMENT '渠道名称' AFTER `media_channel_code`,
|
||||||
|
ADD COLUMN `dept_id` int(11) unsigned NOT NULL DEFAULT 0 COMMENT '绑定部门ID' AFTER `media_channel_name`,
|
||||||
|
ADD COLUMN `dept_name` varchar(100) NOT NULL DEFAULT '' COMMENT '绑定部门名称' AFTER `dept_id`;
|
||||||
|
|
||||||
ALTER TABLE `zyt_account_cost`
|
ALTER TABLE `zyt_account_cost`
|
||||||
DROP INDEX `uk_cost_date`,
|
DROP INDEX `uk_cost_date`,
|
||||||
ADD UNIQUE KEY `uk_cost_date_channel` (`cost_date`, `media_channel_code`),
|
ADD UNIQUE KEY `uk_cost_date_channel_dept` (`cost_date`, `media_channel_code`, `dept_id`),
|
||||||
ADD KEY `idx_cost_date_channel` (`cost_date`, `media_channel_code`);
|
ADD KEY `idx_cost_date_channel_dept` (`cost_date`, `media_channel_code`, `dept_id`);
|
||||||
|
|
||||||
-- 初始化渠道源
|
-- 初始化渠道源
|
||||||
-- php think qywx:scan-media-channel
|
-- php think qywx:scan-media-channel
|
||||||
|
|||||||
Reference in New Issue
Block a user