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:
selfrelease
2026-08-16 14:57:10 +08:00
parent 63a0a6934b
commit a5911d1874
6 changed files with 396 additions and 12 deletions
+3
View File
@@ -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 = {
+43
View File
@@ -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>
)
}