fix: blank_employees 模式不再自动带出基本工资
本月空白模式应所有金额默认 0,去掉从 emp.monthlySalary 自动填充 基本工资的逻辑,保持与"空白"语义一致。 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -556,6 +556,9 @@ export const socialAccountApi = {
|
||||
/** 按部门获取适用账户(选部门时自动带出) */
|
||||
departmentAccount: (departmentId: string) =>
|
||||
get(`/social/department-account/${departmentId}`).then(unwrap<any>()),
|
||||
/** 快速更新当前年度标准的最低工资 */
|
||||
updateMinWage: (accountId: string, minWage: number) =>
|
||||
put(`/social/accounts/${accountId}/min-wage`, { minWage }).then(unwrap<any>()),
|
||||
}
|
||||
|
||||
export const socialInsuranceApi = {
|
||||
|
||||
@@ -38,6 +38,7 @@ export default function SocialInsurance() {
|
||||
const [monthlyMonth, setMonthlyMonth] = useState(new Date().toISOString().slice(0, 7))
|
||||
const [monthlyProcessed, setMonthlyProcessed] = useState(false)
|
||||
const [processStatus, setProcessStatus] = useState<{ social: any; housing: any } | null>(null)
|
||||
const [minWageInput, setMinWageInput] = useState('0')
|
||||
const [newVersion, setNewVersion] = useState<any>({
|
||||
effectiveFrom: new Date().toISOString().slice(0, 7),
|
||||
city: '北京',
|
||||
@@ -238,6 +239,27 @@ export default function SocialInsurance() {
|
||||
},
|
||||
})
|
||||
|
||||
// 快速更新最低工资(无需新建版本)
|
||||
const updateMinWageMutation = useMutation({
|
||||
mutationFn: () => {
|
||||
const accountId = selectedAccountId || activeConfig?.accountId
|
||||
if (!accountId) throw new Error('未选择账户')
|
||||
return socialAccountApi.updateMinWage(accountId, Number(minWageInput) || 0)
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['social-config'] })
|
||||
queryClient.invalidateQueries({ queryKey: ['social-config-versions'] })
|
||||
queryClient.invalidateQueries({ queryKey: ['social-accounts'] })
|
||||
toast.success('最低工资标准已更新')
|
||||
},
|
||||
onError: (err: any) => toast.error(err?.response?.data?.error?.message || '更新失败'),
|
||||
})
|
||||
|
||||
// 当前配置变化时同步最低工资输入框
|
||||
useEffect(() => {
|
||||
setMinWageInput(String(activeConfig?.minWage || 0))
|
||||
}, [activeConfig?.minWage])
|
||||
|
||||
const aiSuggestMut = useMutation<any, any, { city: string; effectiveFrom: string; type: 'social' | 'housing' }>({
|
||||
mutationFn: async (vars: { city: string; effectiveFrom: string; type: 'social' | 'housing' }) => {
|
||||
return await socialInsuranceApi.aiSuggest(vars)
|
||||
@@ -524,6 +546,27 @@ export default function SocialInsurance() {
|
||||
{activeConfig.minWage > 0 && (
|
||||
<div className="flex justify-between border-b pb-1.5"><span className="text-gray-500">最低工资标准</span><span className="font-medium text-primary">¥{fmt(activeConfig.minWage)}</span></div>
|
||||
)}
|
||||
{/* 最低工资快速设置(社保 Tab,当前配置区域可直接编辑) */}
|
||||
<div className="flex justify-between items-center border-b pb-1.5">
|
||||
<span className="text-gray-500">最低工资标准</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
type="number"
|
||||
value={minWageInput}
|
||||
onChange={(e) => setMinWageInput(e.target.value)}
|
||||
placeholder="0"
|
||||
className="w-24 h-7 text-sm text-right"
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onClick={() => updateMinWageMutation.mutate()}
|
||||
disabled={updateMinWageMutation.isPending}
|
||||
>
|
||||
{updateMinWageMutation.isPending ? '保存中...' : '保存'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{Array.isArray(activeConfig.extraInsurances) && activeConfig.extraInsurances.map((ins: any, idx: number) => (
|
||||
<div key={idx} className="flex justify-between border-b pb-1.5">
|
||||
<span className="text-gray-500">{ins.name}(企业/个人)</span>
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { socialAccountApi } from '../../lib/api-services'
|
||||
import api from '../../lib/api'
|
||||
import Button from '../../components/ui/Button'
|
||||
import { Input, Label, Select } from '../../components/ui/Input'
|
||||
import Modal from '../../components/ui/Modal'
|
||||
|
||||
/**
|
||||
* 账户新建/编辑弹窗组件
|
||||
* 从 Settings.tsx 迁移,用于在社保公积金页面中管理账户
|
||||
*/
|
||||
export function AccountFormModal({ type, account, onClose, onSubmit, saving }: {
|
||||
type: string
|
||||
account: any
|
||||
onClose: () => void
|
||||
onSubmit: (data: any, departmentIds?: string[]) => void
|
||||
saving: boolean
|
||||
}) {
|
||||
const [name, setName] = useState(account?.name || '')
|
||||
const [city, setCity] = useState(account?.city || '')
|
||||
const [accountNo, setAccountNo] = useState(account?.accountNo || '')
|
||||
const [orgName, setOrgName] = useState(account?.orgName || '')
|
||||
const [orgCode, setOrgCode] = useState(account?.orgCode || '')
|
||||
const [bankName, setBankName] = useState(account?.bankName || '')
|
||||
const [bankAccount, setBankAccount] = useState(account?.bankAccount || '')
|
||||
const [accountTypeVal, setAccountTypeVal] = useState(account?.accountType || 'BASIC')
|
||||
const [isDefault, setIsDefault] = useState(account?.isDefault || false)
|
||||
const [remark, setRemark] = useState(account?.remark || '')
|
||||
const [selectedDeptIds, setSelectedDeptIds] = useState<string[]>([])
|
||||
|
||||
// 加载 level=0 根部门列表
|
||||
const { data: rootDepartments = [] } = useQuery<any[]>({
|
||||
queryKey: ['departments'],
|
||||
queryFn: () => api.get('/departments').then(r => r.data),
|
||||
})
|
||||
const rootDepts = rootDepartments.filter((d: any) => d.level === 0)
|
||||
|
||||
// 编辑时加载已关联部门
|
||||
useEffect(() => {
|
||||
if (account?.id) {
|
||||
socialAccountApi.accountDepartments(account.id).then((depts: any[]) => {
|
||||
setSelectedDeptIds(depts.map((d: any) => d.id))
|
||||
}).catch(() => {})
|
||||
}
|
||||
}, [account?.id])
|
||||
|
||||
const toggleDept = (id: string) => {
|
||||
setSelectedDeptIds(prev => prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id])
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal open onClose={onClose} title={account ? '编辑账户' : '新建账户'} size="md">
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label>账户名称 *</Label>
|
||||
<Input value={name} onChange={(e) => setName(e.target.value)} placeholder="如:北京总公司社保账户" />
|
||||
</div>
|
||||
<div>
|
||||
<Label>参保城市 *</Label>
|
||||
<Input value={city} onChange={(e) => setCity(e.target.value)} placeholder="如:北京" />
|
||||
</div>
|
||||
<div>
|
||||
<Label>{type === 'SOCIAL' ? '社保登记号' : '公积金单位账号'}</Label>
|
||||
<Input value={accountNo} onChange={(e) => setAccountNo(e.target.value)} />
|
||||
</div>
|
||||
{type === 'HOUSING' && (
|
||||
<>
|
||||
<div>
|
||||
<Label>账户类型</Label>
|
||||
<Select value={accountTypeVal} onChange={(e) => setAccountTypeVal(e.target.value)}>
|
||||
<option value="BASIC">基本公积金</option>
|
||||
<option value="SUPPLEMENTARY">补充公积金</option>
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<Label>开户行</Label>
|
||||
<Input value={bankName} onChange={(e) => setBankName(e.target.value)} placeholder="如:工商银行北京分行" />
|
||||
</div>
|
||||
<div>
|
||||
<Label>银行账号</Label>
|
||||
<Input value={bankAccount} onChange={(e) => setBankAccount(e.target.value)} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div>
|
||||
<Label>缴费主体名称</Label>
|
||||
<Input value={orgName} onChange={(e) => setOrgName(e.target.value)} placeholder="子公司/分公司名称" />
|
||||
</div>
|
||||
<div>
|
||||
<Label>缴费主体统一社会信用代码</Label>
|
||||
<Input value={orgCode} onChange={(e) => setOrgCode(e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
<Label>备注</Label>
|
||||
<Input value={remark} onChange={(e) => setRemark(e.target.value)} />
|
||||
</div>
|
||||
|
||||
{/* 关联根部门 */}
|
||||
<div>
|
||||
<Label>关联根部门(公司/分公司/子公司)</Label>
|
||||
<p className="text-xs text-gray-400 mb-2">勾选后,这些根部门下的员工将自动继承此账户。未勾选则使用公司默认账户。</p>
|
||||
{rootDepts.length === 0 ? (
|
||||
<div className="text-xs text-gray-400">暂无根部门</div>
|
||||
) : (
|
||||
<div className="space-y-1 max-h-40 overflow-y-auto border rounded-md p-2">
|
||||
{rootDepts.map((d: any) => (
|
||||
<label key={d.id} className="flex items-center gap-2 text-sm py-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedDeptIds.includes(d.id)}
|
||||
onChange={() => toggleDept(d.id)}
|
||||
/>
|
||||
<span>{d.name}</span>
|
||||
{d._count?.employees > 0 && <span className="text-xs text-gray-400">({d._count.employees}人)</span>}
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<label className="flex items-center gap-2 text-sm">
|
||||
<input type="checkbox" checked={isDefault} onChange={(e) => setIsDefault(e.target.checked)} />
|
||||
设为默认账户(同类型下仅一个默认)
|
||||
</label>
|
||||
<div className="flex justify-end gap-2 pt-2">
|
||||
<Button variant="secondary" onClick={onClose}>取消</Button>
|
||||
<Button
|
||||
disabled={!name || !city || saving}
|
||||
onClick={() => onSubmit({ name, city, accountNo, orgName, orgCode, bankName, bankAccount, accountType: accountTypeVal, isDefault, remark }, selectedDeptIds)}
|
||||
>
|
||||
{saving ? '保存中...' : '保存'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user