增加工资发放
This commit is contained in:
parent
c7f7a7047f
commit
5e51b460f4
@ -138,6 +138,22 @@ export async function pageSalary(params: Record<string, any>) {
|
||||
);
|
||||
}
|
||||
|
||||
export async function pageManualSalary(params: Record<string, any>) {
|
||||
return requestClient.get<LegacyPageResult<Record<string, any>>>(
|
||||
'/team/salary/manual/page',
|
||||
{
|
||||
params,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
export async function confirmManualSalary(data: Record<string, any>) {
|
||||
return requestClient.post<Array<Record<string, any>>>(
|
||||
'/team/salary/manual/pay',
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
export async function pageCpApply(params: Record<string, any>) {
|
||||
return requestClient.get<LegacyPageResult<Record<string, any>>>(
|
||||
'/user-cp-apply/page',
|
||||
|
||||
@ -25,6 +25,13 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('#/views/operate/auto-salary-pay-record.vue'),
|
||||
meta: { title: '自动发放工资凭据' },
|
||||
},
|
||||
{
|
||||
name: 'ManualSalaryPayment',
|
||||
path: 'manual-salary-payment',
|
||||
alias: '/manual-salary-payment',
|
||||
component: () => import('#/views/operate/manual-salary-payment.vue'),
|
||||
meta: { title: '工资发放' },
|
||||
},
|
||||
{
|
||||
name: 'UserBankWithdrawMoneyApply',
|
||||
path: 'withdraw-money-apply-page',
|
||||
|
||||
379
apps/src/views/operate/manual-salary-payment.vue
Normal file
379
apps/src/views/operate/manual-salary-payment.vue
Normal file
@ -0,0 +1,379 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
computed,
|
||||
reactive,
|
||||
ref,
|
||||
watch,
|
||||
} from 'vue';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
import { useAccessStore } from '@vben/stores';
|
||||
|
||||
import { getCountryAlls } from '#/api/legacy/system';
|
||||
import {
|
||||
confirmManualSalary,
|
||||
pageManualSalary,
|
||||
} from '#/api/legacy/operate';
|
||||
import AccountInput from '#/components/account-input.vue';
|
||||
import { getAllowedSysOrigins } from '#/views/system/shared';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Modal,
|
||||
Pagination,
|
||||
Select,
|
||||
Space,
|
||||
Table,
|
||||
Tag,
|
||||
message,
|
||||
} from 'antdv-next';
|
||||
|
||||
import UserProfileLink from './components/user-profile-link.vue';
|
||||
|
||||
defineOptions({ name: 'OperateManualSalaryPayment' });
|
||||
|
||||
const accessStore = useAccessStore();
|
||||
const sysOriginOptions = computed(() => {
|
||||
const options = getAllowedSysOrigins(accessStore.accessCodes || []);
|
||||
return options.length > 0 ? options : getAllowedSysOrigins([]);
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
const paying = ref(false);
|
||||
const total = ref(0);
|
||||
const list = ref<Array<Record<string, any>>>([]);
|
||||
const countries = ref<Array<Record<string, any>>>([]);
|
||||
const countryKeyword = ref('');
|
||||
const resultOpen = ref(false);
|
||||
const resultRows = ref<Array<Record<string, any>>>([]);
|
||||
const selectedUserIds = ref<string[]>([]);
|
||||
|
||||
const query = reactive({
|
||||
countryCode: '',
|
||||
cursor: 1,
|
||||
limit: 20,
|
||||
sysOrigin: '',
|
||||
userId: '',
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{ dataIndex: 'userProfile', key: 'userProfile', title: '用户信息', width: 300 },
|
||||
{ dataIndex: 'countryCode', key: 'countryCode', title: '国家', width: 90 },
|
||||
{ dataIndex: 'lastSettlementTarget', key: 'lastSettlementTarget', title: '上次结算的积分', width: 150 },
|
||||
{ dataIndex: 'currentTarget', key: 'currentTarget', title: '本次积分', width: 130 },
|
||||
{ dataIndex: 'policyLevel', key: 'policyLevel', title: '本次积分档位', width: 130 },
|
||||
{ dataIndex: 'payableSalary', key: 'payableSalary', title: '应发工资', width: 120 },
|
||||
{ dataIndex: 'totalIssuedSalary', key: 'totalIssuedSalary', title: '总发放工资', width: 130 },
|
||||
{ dataIndex: 'currentSalaryBalance', key: 'currentSalaryBalance', title: '当前工资余额', width: 140 },
|
||||
];
|
||||
|
||||
const resultColumns = [
|
||||
{ dataIndex: 'userProfile', key: 'userProfile', title: '用户信息', width: 280 },
|
||||
{ dataIndex: 'countryCode', key: 'countryCode', title: '国家', width: 90 },
|
||||
{ dataIndex: 'salaryChange', key: 'salaryChange', title: '工资变动', width: 180 },
|
||||
{ dataIndex: 'issuedSalary', key: 'issuedSalary', title: '发放工资', width: 120 },
|
||||
{ dataIndex: 'policyLevel', key: 'policyLevel', title: '档位', width: 90 },
|
||||
{ dataIndex: 'currentTarget', key: 'currentTarget', title: '本次积分', width: 120 },
|
||||
];
|
||||
|
||||
const selectedUserCount = computed(() => selectedUserIds.value.length);
|
||||
|
||||
const confirmButtonText = computed(() =>
|
||||
selectedUserCount.value > 0
|
||||
? `确认发放(${selectedUserCount.value}人)`
|
||||
: '确认发放',
|
||||
);
|
||||
|
||||
const rowSelection = computed(() => ({
|
||||
getCheckboxProps: (record: Record<string, any>) => ({
|
||||
disabled: !record.userId,
|
||||
}),
|
||||
onChange: (keys: Array<number | string>) => {
|
||||
selectedUserIds.value = keys.map(String).filter(Boolean);
|
||||
},
|
||||
preserveSelectedRowKeys: true,
|
||||
selectedRowKeys: selectedUserIds.value,
|
||||
}));
|
||||
|
||||
const filteredCountries = computed(() => {
|
||||
const keyword = countryKeyword.value.trim().toLowerCase();
|
||||
if (!keyword) {
|
||||
return countries.value;
|
||||
}
|
||||
return countries.value.filter((item) =>
|
||||
[
|
||||
item.phonePrefix,
|
||||
item.countryName,
|
||||
item.aliasName,
|
||||
item.alphaTwo,
|
||||
item.alphaThree,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
.toLowerCase()
|
||||
.includes(keyword),
|
||||
);
|
||||
});
|
||||
|
||||
watch(
|
||||
sysOriginOptions,
|
||||
(options) => {
|
||||
if (!query.sysOrigin && options.length > 0) {
|
||||
query.sysOrigin = String(options[0]?.value || '');
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
[() => query.sysOrigin, () => query.countryCode, () => query.userId],
|
||||
() => {
|
||||
selectedUserIds.value = [];
|
||||
},
|
||||
);
|
||||
|
||||
async function loadCountries() {
|
||||
countries.value = (await getCountryAlls()) || [];
|
||||
}
|
||||
|
||||
async function loadData(reset = false) {
|
||||
if (!query.sysOrigin) {
|
||||
return;
|
||||
}
|
||||
if (!query.countryCode) {
|
||||
message.warning('请选择国家');
|
||||
return;
|
||||
}
|
||||
if (reset) {
|
||||
query.cursor = 1;
|
||||
selectedUserIds.value = [];
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
const result = await pageManualSalary({ ...query });
|
||||
list.value = result.records || [];
|
||||
total.value = Number(result.total || 0);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handlePageChange(page: number, pageSize: number) {
|
||||
query.cursor = page;
|
||||
query.limit = pageSize;
|
||||
void loadData();
|
||||
}
|
||||
|
||||
function formatMoney(value?: number | string) {
|
||||
const amount = Number(value || 0);
|
||||
if (Number.isNaN(amount)) {
|
||||
return '$0.00';
|
||||
}
|
||||
return `$${amount.toFixed(2)}`;
|
||||
}
|
||||
|
||||
function formatInteger(value?: number | string) {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
return '-';
|
||||
}
|
||||
return Number(value).toLocaleString('en-US');
|
||||
}
|
||||
|
||||
function countryLabel(code?: string) {
|
||||
const country = countries.value.find((item) => item.alphaTwo === code);
|
||||
if (!country) {
|
||||
return code || '-';
|
||||
}
|
||||
return `${country.phonePrefix || ''} ${country.countryName || country.aliasName || code}`;
|
||||
}
|
||||
|
||||
function getRowKey(record: Record<string, any>) {
|
||||
return String(record.userId || record.id || '');
|
||||
}
|
||||
|
||||
function confirmPay() {
|
||||
if (!query.countryCode) {
|
||||
message.warning('请选择国家');
|
||||
return;
|
||||
}
|
||||
|
||||
const userIds = [...selectedUserIds.value];
|
||||
const hasSelectedUsers = userIds.length > 0;
|
||||
|
||||
Modal.confirm({
|
||||
content: hasSelectedUsers
|
||||
? '本次只发放已勾选的用户。'
|
||||
: '未勾选用户,本次将发放当前筛选国家的所有可发工资用户。',
|
||||
title: hasSelectedUsers
|
||||
? `确认发放已选择的 ${userIds.length} 人当前账期工资?`
|
||||
: `确认发放 ${countryLabel(query.countryCode)} 当前账期工资?`,
|
||||
async onOk() {
|
||||
paying.value = true;
|
||||
try {
|
||||
resultRows.value = await confirmManualSalary({
|
||||
countryCode: query.countryCode,
|
||||
sysOrigin: query.sysOrigin,
|
||||
...(hasSelectedUsers ? { userIds } : {}),
|
||||
});
|
||||
resultOpen.value = true;
|
||||
selectedUserIds.value = [];
|
||||
message.success('发放成功');
|
||||
await loadData();
|
||||
} finally {
|
||||
paying.value = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
void loadCountries();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page title="工资发放">
|
||||
<Card>
|
||||
<div class="toolbar">
|
||||
<Space wrap>
|
||||
<SysOriginSelect
|
||||
v-model:value="query.sysOrigin"
|
||||
:options="sysOriginOptions"
|
||||
style="width: 140px"
|
||||
/>
|
||||
<Select
|
||||
v-model:value="query.countryCode"
|
||||
allow-clear
|
||||
show-search
|
||||
:filter-option="false"
|
||||
option-label-prop="label"
|
||||
placeholder="国家"
|
||||
style="width: 240px"
|
||||
:options="filteredCountries.map((item) => ({
|
||||
label: `${item.phonePrefix || ''} ${item.countryName || item.aliasName || ''}`,
|
||||
value: item.alphaTwo as any,
|
||||
}))"
|
||||
@change="loadData(true)"
|
||||
@search="countryKeyword = $event"
|
||||
/>
|
||||
<AccountInput
|
||||
v-model:value="query.userId"
|
||||
:sys-origin="query.sysOrigin"
|
||||
placeholder="用户ID"
|
||||
style="width: 260px"
|
||||
/>
|
||||
<Button :loading="loading" type="primary" @click="loadData(true)">
|
||||
搜索
|
||||
</Button>
|
||||
<Button
|
||||
danger
|
||||
:disabled="!query.countryCode"
|
||||
:loading="paying"
|
||||
@click="confirmPay"
|
||||
>
|
||||
{{ confirmButtonText }}
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
:columns="columns"
|
||||
:data-source="list"
|
||||
:loading="loading"
|
||||
:pagination="false"
|
||||
:row-key="getRowKey"
|
||||
:row-selection="rowSelection"
|
||||
:scroll="{ x: 1400 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'userProfile'">
|
||||
<UserProfileLink :profile="record.userProfile" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'countryCode'">
|
||||
{{ record.countryCode || '-' }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'lastSettlementTarget'">
|
||||
{{ formatInteger(record.lastSettlementTarget) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'currentTarget'">
|
||||
{{ formatInteger(record.currentTarget) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'policyLevel'">
|
||||
<Tag v-if="record.policyLevel" color="blue">
|
||||
{{ record.policyLevel }}
|
||||
</Tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'payableSalary'">
|
||||
<Space size="small">
|
||||
<span>{{ formatMoney(record.payableSalary) }}</span>
|
||||
<Tag v-if="record.paid" color="green">已发</Tag>
|
||||
</Space>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'totalIssuedSalary'">
|
||||
{{ formatMoney(record.totalIssuedSalary) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'currentSalaryBalance'">
|
||||
{{ formatMoney(record.currentSalaryBalance) }}
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
|
||||
<div class="pager">
|
||||
<Pagination
|
||||
:current="query.cursor"
|
||||
:page-size="query.limit"
|
||||
:total="total"
|
||||
show-size-changer
|
||||
@change="handlePageChange"
|
||||
@showSizeChange="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Modal
|
||||
v-model:open="resultOpen"
|
||||
:footer="null"
|
||||
title="发放结果"
|
||||
width="980px"
|
||||
>
|
||||
<Table
|
||||
:columns="resultColumns"
|
||||
:data-source="resultRows"
|
||||
:pagination="false"
|
||||
row-key="id"
|
||||
size="small"
|
||||
:scroll="{ x: 900, y: 520 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'userProfile'">
|
||||
<UserProfileLink :profile="record.userProfile" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'salaryChange'">
|
||||
{{ formatMoney(record.beforeSalaryBalance) }} -> {{ formatMoney(record.afterSalaryBalance) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'issuedSalary'">
|
||||
{{ formatMoney(record.issuedSalary) }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'policyLevel'">
|
||||
<Tag color="blue">{{ record.policyLevel || '-' }}</Tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'currentTarget'">
|
||||
{{ formatInteger(record.currentTarget) }}
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
</Modal>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.toolbar {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.pager {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
76
scripts/sql/sync_manual_salary_payment_menu.sql
Normal file
76
scripts/sql/sync_manual_salary_payment_menu.sql
Normal file
@ -0,0 +1,76 @@
|
||||
SET NAMES utf8mb4;
|
||||
|
||||
SET @app_user_manager_id := (
|
||||
SELECT id
|
||||
FROM sys_menu
|
||||
WHERE alias IN ('AppUserManager', 'OperateManager')
|
||||
ORDER BY FIELD(alias, 'AppUserManager', 'OperateManager')
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
INSERT INTO sys_menu (
|
||||
id,
|
||||
parent_id,
|
||||
menu_name,
|
||||
path,
|
||||
menu_type,
|
||||
icon,
|
||||
create_user,
|
||||
update_user,
|
||||
sort,
|
||||
status,
|
||||
router,
|
||||
alias
|
||||
)
|
||||
SELECT
|
||||
menu_id.next_id,
|
||||
@app_user_manager_id,
|
||||
'工资发放',
|
||||
'operate/manual-salary-payment',
|
||||
2,
|
||||
'form',
|
||||
'0',
|
||||
'0',
|
||||
49,
|
||||
0,
|
||||
'/manual-salary-payment',
|
||||
'ManualSalaryPayment'
|
||||
FROM (
|
||||
SELECT COALESCE(MAX(id), 1700000000) + 1 AS next_id
|
||||
FROM sys_menu
|
||||
) AS menu_id
|
||||
WHERE @app_user_manager_id IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM sys_menu WHERE alias = 'ManualSalaryPayment'
|
||||
);
|
||||
|
||||
UPDATE sys_menu
|
||||
SET
|
||||
parent_id = @app_user_manager_id,
|
||||
menu_name = '工资发放',
|
||||
path = 'operate/manual-salary-payment',
|
||||
menu_type = 2,
|
||||
icon = 'form',
|
||||
update_user = '0',
|
||||
sort = 49,
|
||||
status = 0,
|
||||
router = '/manual-salary-payment'
|
||||
WHERE alias = 'ManualSalaryPayment'
|
||||
AND @app_user_manager_id IS NOT NULL;
|
||||
|
||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||
SELECT DISTINCT
|
||||
role_menu.role_id,
|
||||
target_menu.id
|
||||
FROM sys_role_menu AS role_menu
|
||||
JOIN sys_menu AS source_menu
|
||||
ON source_menu.id = role_menu.menu_id
|
||||
AND source_menu.alias IN ('AutoSalaryPayRecord', 'AppUserManager', 'OperateManager')
|
||||
JOIN sys_menu AS target_menu
|
||||
ON target_menu.alias = 'ManualSalaryPayment'
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM sys_role_menu AS existing_role_menu
|
||||
WHERE existing_role_menu.role_id = role_menu.role_id
|
||||
AND existing_role_menu.menu_id = target_menu.id
|
||||
);
|
||||
Loading…
x
Reference in New Issue
Block a user