|
|
@@ -24,6 +24,10 @@
|
|
|
<multi-calendar-view :vm="vm" />
|
|
|
</el-tab-pane>
|
|
|
|
|
|
+ <el-tab-pane label="冲突检测" name="conflict">
|
|
|
+ <conflict-panel ref="conflictPanel" :vm="vm" />
|
|
|
+ </el-tab-pane>
|
|
|
+
|
|
|
<el-tab-pane label="临时调整审批" name="adjust">
|
|
|
<adjust-approval ref="adjustApproval" :vm="vm" />
|
|
|
</el-tab-pane>
|
|
|
@@ -740,6 +744,7 @@
|
|
|
import { solarToLunar } from 'lunar-calendar';
|
|
|
import BaseManage from './components/BaseManage.vue';
|
|
|
import MultiCalendarView from './components/MultiCalendarView.vue';
|
|
|
+ import ConflictPanel from './components/ConflictPanel.vue';
|
|
|
import AdjustApproval from './components/AdjustApproval.vue';
|
|
|
import StatisticsView from './components/StatisticsView.vue';
|
|
|
import {
|
|
|
@@ -873,6 +878,7 @@
|
|
|
components: {
|
|
|
BaseManage,
|
|
|
MultiCalendarView,
|
|
|
+ ConflictPanel,
|
|
|
AdjustApproval,
|
|
|
StatisticsView
|
|
|
},
|
|
|
@@ -1399,29 +1405,80 @@
|
|
|
);
|
|
|
},
|
|
|
monthStats() {
|
|
|
- const visibleDays = this.statCells;
|
|
|
- const workDays = visibleDays.filter((item) => !item.isRest).length;
|
|
|
- const scheduledDays = visibleDays.filter(
|
|
|
- (item) => !item.isRest && item.scheduleStatus
|
|
|
- ).length;
|
|
|
- const conflictDays = visibleDays.filter(
|
|
|
- (item) => item.isConflict
|
|
|
- ).length;
|
|
|
- const tempDays = visibleDays.filter((item) => item.isTempAdjust).length;
|
|
|
- const restDays = visibleDays.filter((item) => item.isRest).length;
|
|
|
+ const details = this.getStatMonthDetails();
|
|
|
+ const countDistinctDates = (predicate) =>
|
|
|
+ new Set(
|
|
|
+ details
|
|
|
+ .filter(predicate)
|
|
|
+ .map((item) => item.calendarDate)
|
|
|
+ .filter(Boolean)
|
|
|
+ ).size;
|
|
|
+ const workDays = countDistinctDates(
|
|
|
+ (item) => Number(item.dateType) === 1
|
|
|
+ );
|
|
|
+ const scheduledDateSet = new Set(
|
|
|
+ details
|
|
|
+ .filter((item) => this.isDetailScheduled(item))
|
|
|
+ .map((item) => item.calendarDate)
|
|
|
+ .filter(Boolean)
|
|
|
+ );
|
|
|
+ const scheduledDays = scheduledDateSet.size;
|
|
|
+ const scheduledWorkdaySet = new Set(
|
|
|
+ details
|
|
|
+ .filter(
|
|
|
+ (item) =>
|
|
|
+ Number(item.dateType) === 1 &&
|
|
|
+ scheduledDateSet.has(item.calendarDate)
|
|
|
+ )
|
|
|
+ .map((item) => item.calendarDate)
|
|
|
+ );
|
|
|
+ const emptyWorkdaySet = new Set(
|
|
|
+ details
|
|
|
+ .filter(
|
|
|
+ (item) =>
|
|
|
+ Number(item.dateType) === 1 &&
|
|
|
+ !scheduledDateSet.has(item.calendarDate)
|
|
|
+ )
|
|
|
+ .map((item) => item.calendarDate)
|
|
|
+ );
|
|
|
+ const idleDays = Math.max(
|
|
|
+ emptyWorkdaySet.size,
|
|
|
+ workDays - scheduledWorkdaySet.size,
|
|
|
+ 0
|
|
|
+ );
|
|
|
+ const conflictDays = countDistinctDates(
|
|
|
+ (item) => Number(item.isConflict) === 1
|
|
|
+ );
|
|
|
+ const tempDays = countDistinctDates(
|
|
|
+ (item) => Number(item.isTempAdjust) === 1
|
|
|
+ );
|
|
|
+ const restDays = countDistinctDates((item) =>
|
|
|
+ [2, 3].includes(Number(item.dateType))
|
|
|
+ );
|
|
|
return [
|
|
|
{ key: 'work', label: '总工作日', value: workDays },
|
|
|
{ key: 'scheduled', label: '已排班天数', value: scheduledDays },
|
|
|
{
|
|
|
key: 'idle',
|
|
|
label: '未排班天数',
|
|
|
- value: Math.max(workDays - scheduledDays, 0)
|
|
|
+ value: idleDays
|
|
|
},
|
|
|
{ key: 'conflict', label: '冲突天数', value: conflictDays },
|
|
|
{ key: 'temp', label: '临时调整天数', value: tempDays },
|
|
|
{ key: 'rest', label: '休息/节假日', value: restDays }
|
|
|
];
|
|
|
},
|
|
|
+ scheduledWorkdayDays() {
|
|
|
+ return new Set(
|
|
|
+ this.getStatMonthDetails()
|
|
|
+ .filter(
|
|
|
+ (item) =>
|
|
|
+ Number(item.dateType) === 1 && this.isDetailScheduled(item)
|
|
|
+ )
|
|
|
+ .map((item) => item.calendarDate)
|
|
|
+ .filter(Boolean)
|
|
|
+ ).size;
|
|
|
+ },
|
|
|
dayDrawerTitle() {
|
|
|
return this.currentDay.date
|
|
|
? `${this.currentDay.date} 日历详情`
|
|
|
@@ -1542,9 +1599,12 @@
|
|
|
scheduleRate() {
|
|
|
const workDays =
|
|
|
this.monthStats.find((item) => item.key === 'work')?.value || 0;
|
|
|
- const scheduledDays =
|
|
|
- this.monthStats.find((item) => item.key === 'scheduled')?.value || 0;
|
|
|
- return workDays ? Math.round((scheduledDays / workDays) * 100) : 0;
|
|
|
+ return workDays
|
|
|
+ ? Math.min(
|
|
|
+ 100,
|
|
|
+ Math.round((this.scheduledWorkdayDays / workDays) * 100)
|
|
|
+ )
|
|
|
+ : 0;
|
|
|
},
|
|
|
scheduleSegments() {
|
|
|
const scheduled =
|
|
|
@@ -1627,9 +1687,15 @@
|
|
|
'0'
|
|
|
)}-01`
|
|
|
).date(index * 5 + 1);
|
|
|
- const count = this.statCells.filter(
|
|
|
- (item) => item.isConflict && dayjs(item.date).isSame(date, 'week')
|
|
|
- ).length;
|
|
|
+ const count = new Set(
|
|
|
+ this.getStatMonthDetails()
|
|
|
+ .filter(
|
|
|
+ (item) =>
|
|
|
+ Number(item.isConflict) === 1 &&
|
|
|
+ dayjs(item.calendarDate).isSame(date, 'week')
|
|
|
+ )
|
|
|
+ .map((item) => item.calendarDate)
|
|
|
+ ).size;
|
|
|
return {
|
|
|
label: date.format('MM/DD'),
|
|
|
count
|
|
|
@@ -1864,9 +1930,10 @@
|
|
|
this.viewDataPromise = null;
|
|
|
return;
|
|
|
}
|
|
|
- const remoteDetails = data.map((item) =>
|
|
|
- this.normalizeRemoteDetail(item)
|
|
|
- );
|
|
|
+ const remoteDetails = data.map((item) => ({
|
|
|
+ ...this.normalizeRemoteDetail(item),
|
|
|
+ isViewDetail: true
|
|
|
+ }));
|
|
|
const visibleDates =
|
|
|
queryOverride && query.viewType === 'month'
|
|
|
? this.getMonthDateRange(query.year, query.month).map((item) =>
|
|
|
@@ -2249,7 +2316,17 @@
|
|
|
return (data.conflictDetail || []).map((item, index) => ({
|
|
|
id:
|
|
|
item.currentDetailId ||
|
|
|
- `${item.calendarDate}-${item.startTime}-${item.endTime}-${index}`,
|
|
|
+ [
|
|
|
+ item.calendarDate,
|
|
|
+ item.startTime,
|
|
|
+ item.endTime,
|
|
|
+ item.currentCalendarType,
|
|
|
+ item.conflictCalendarType,
|
|
|
+ item.conflictCalendarId,
|
|
|
+ item.conflictDetailId,
|
|
|
+ item.conflictMsg || data.msg || '',
|
|
|
+ index
|
|
|
+ ].join('-'),
|
|
|
calendarDate: item.calendarDate,
|
|
|
timeRange: `${String(item.startTime || '').slice(0, 5)}-${String(
|
|
|
item.endTime || ''
|
|
|
@@ -3277,12 +3354,54 @@
|
|
|
});
|
|
|
},
|
|
|
getViewDetails(date) {
|
|
|
- const calendarIds = this.getCurrentViewCalendars().map(
|
|
|
- (item) => item.id
|
|
|
- );
|
|
|
return this.details.filter(
|
|
|
(item) =>
|
|
|
- calendarIds.includes(item.calendarId) && item.calendarDate === date
|
|
|
+ this.isDetailInCurrentView(item) && item.calendarDate === date
|
|
|
+ );
|
|
|
+ },
|
|
|
+ getStatMonthDetails() {
|
|
|
+ const monthStart = dayjs(
|
|
|
+ `${this.viewQuery.year}-${String(this.viewQuery.month).padStart(
|
|
|
+ 2,
|
|
|
+ '0'
|
|
|
+ )}-01`
|
|
|
+ );
|
|
|
+ const monthEnd = monthStart.endOf('month');
|
|
|
+ return this.details.filter((item) => {
|
|
|
+ const date = dayjs(item.calendarDate);
|
|
|
+ return (
|
|
|
+ this.isDetailInCurrentView(item) &&
|
|
|
+ date.isValid() &&
|
|
|
+ (date.isSame(monthStart, 'day') || date.isAfter(monthStart)) &&
|
|
|
+ (date.isSame(monthEnd, 'day') || date.isBefore(monthEnd))
|
|
|
+ );
|
|
|
+ });
|
|
|
+ },
|
|
|
+ isDetailInCurrentView(item = {}) {
|
|
|
+ const targetType = Number(this.viewQuery.calendarType);
|
|
|
+ const detailType = Number(item.calendarType);
|
|
|
+ const typeMatched = targetType ? detailType === targetType : true;
|
|
|
+ const calendarIds = new Set(
|
|
|
+ this.getCurrentViewCalendars().map((calendar) => String(calendar.id))
|
|
|
+ );
|
|
|
+ const hasCalendarId =
|
|
|
+ item.calendarId !== undefined &&
|
|
|
+ item.calendarId !== null &&
|
|
|
+ item.calendarId !== '';
|
|
|
+
|
|
|
+ if (hasCalendarId && calendarIds.has(String(item.calendarId))) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ if (item.isViewDetail) {
|
|
|
+ return typeMatched;
|
|
|
+ }
|
|
|
+ return !hasCalendarId && typeMatched;
|
|
|
+ },
|
|
|
+ isDetailScheduled(item = {}) {
|
|
|
+ return (
|
|
|
+ Number(item.scheduleStatus) === 1 ||
|
|
|
+ Number(item.relationPlanCount || 0) > 0 ||
|
|
|
+ this.getDetailRelatedPlans([item]).length > 0
|
|
|
);
|
|
|
},
|
|
|
isWeekend(date) {
|
|
|
@@ -3717,9 +3836,14 @@
|
|
|
getConflictDataKey() {
|
|
|
return [
|
|
|
this.viewQuery.year || '',
|
|
|
- this.viewQuery.calendarType || ''
|
|
|
+ this.getConflictCheckTypes().join(',')
|
|
|
].join('|');
|
|
|
},
|
|
|
+ getConflictCheckTypes() {
|
|
|
+ return this.calendarTypeOptions
|
|
|
+ .map((item) => Number(item.value))
|
|
|
+ .filter((type) => [1, 2, 3].includes(type));
|
|
|
+ },
|
|
|
invalidateConflictCache() {
|
|
|
this.conflictDataKey = '';
|
|
|
},
|
|
|
@@ -3734,12 +3858,19 @@
|
|
|
return;
|
|
|
}
|
|
|
try {
|
|
|
- this.conflictCheckPromise = checkAllCalendarConflict({
|
|
|
- year: this.viewQuery.year,
|
|
|
- calendarType: this.viewQuery.calendarType || undefined
|
|
|
- });
|
|
|
- const data = await this.conflictCheckPromise;
|
|
|
- this.conflictList = this.normalizeRemoteConflicts(data);
|
|
|
+ const conflictTypes = this.getConflictCheckTypes();
|
|
|
+ this.conflictCheckPromise = Promise.all(
|
|
|
+ conflictTypes.map((calendarType) =>
|
|
|
+ checkAllCalendarConflict({
|
|
|
+ year: this.viewQuery.year,
|
|
|
+ calendarType
|
|
|
+ })
|
|
|
+ )
|
|
|
+ );
|
|
|
+ const list = await this.conflictCheckPromise;
|
|
|
+ this.conflictList = this.uniqueConflicts(
|
|
|
+ list.flatMap((data) => this.normalizeRemoteConflicts(data))
|
|
|
+ );
|
|
|
this.conflictDataKey = dataKey;
|
|
|
this.reloadConflictTable(1);
|
|
|
if (showMessage) {
|