feat: 最低工资保护 + 社保/最低工资递延补扣机制

新增功能:
1. SocialYearStandard / SocialInsuranceConfig 增加 minWage 字段
2. BatchEntry 增加递延扣款字段(deferredSocialEmp/HousingEmp/MinWage)
3. calcBatchEntry 增加最低工资保护逻辑:
   - 实发 < 最低工资时,优先递延社保 → 递延公积金 → 递延最低工资补齐
   - 递延金额记录到 BatchEntry,次月创建批次时自动补扣
4. prePayrollCheck 增加最低工资检查(第 11/12 项)
5. 前端社保配置增加最低工资输入框
6. 前端 BatchTab 质量门禁增加最低工资/递延扣款提示

处理场景:
- 入职当月工资不足扣社保个人部分 → 递延到次月补扣
- 实发低于最低工资 → 补齐到最低工资,差额递延次月扣
- 次月创建批次时自动读取上月递延金额并补扣

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:15:39 +08:00
parent fe427ef13f
commit ea2dfe3b4b
6 changed files with 221 additions and 14 deletions
+13
View File
@@ -47,6 +47,7 @@ export default function SocialInsurance() {
injuryOrg: 0.2, maternityOrg: 0.8,
baseMin: 6326, baseMax: 33891,
medicalBaseMin: 0, medicalBaseMax: 0,
minWage: 0,
extraInsurances: [],
})
const [newHousingVersion, setNewHousingVersion] = useState<any>({
@@ -520,6 +521,9 @@ export default function SocialInsurance() {
<div className="flex justify-between border-b pb-1.5"><span className="text-gray-500">(/)</span><span className="font-medium">{activeConfig.unemploymentOrg}% / {activeConfig.unemploymentEmp}%</span></div>
<div className="flex justify-between border-b pb-1.5"><span className="text-gray-500">()</span><span className="font-medium">{activeConfig.injuryOrg}%</span></div>
<div className="flex justify-between border-b pb-1.5"><span className="text-gray-500">()</span><span className="font-medium">{activeConfig.maternityOrg}%</span></div>
{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>
)}
{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>
@@ -723,6 +727,15 @@ export default function SocialInsurance() {
</div>
</div>
)}
{!isHousing && (
<div className="grid md:grid-cols-3 gap-3">
<div>
<Label></Label>
<Input type="number" value={activeNewVersion.minWage} onChange={(e) => activeSetNewVersion({ ...activeNewVersion, minWage: Number(e.target.value) })} placeholder="如 2420" />
<p className="text-xs text-gray-400 mt-1"> 0 </p>
</div>
</div>
)}
{isHousing ? (
<>
<div className="grid md:grid-cols-3 gap-3">
+15
View File
@@ -964,6 +964,21 @@ function BatchDetail({ batchId, onBack }: { batchId: string; onBack: () => void
/ 0
</InlineAlert>
)}
{batch.entries.some((e: any) => e.minWage > 0 && e.netPay < e.minWage && (e.minWageApplied || 0) === 0) && (
<InlineAlert type="error" title="存在实发低于最低工资的员工">
¥{Math.min(...batch.entries.filter((e: any) => e.minWage > 0 && e.netPay < e.minWage).map((e: any) => e.minWage))}
</InlineAlert>
)}
{batch.entries.some((e: any) => (e.minWageApplied || 0) > 0) && (
<InlineAlert type="warning" title="最低工资保护已触发">
{batch.entries.filter((e: any) => (e.minWageApplied || 0) > 0).length} //
</InlineAlert>
)}
{batch.entries.some((e: any) => (e.prevDeferredSocialEmp || 0) + (e.prevDeferredHousingEmp || 0) + (e.prevDeferredMinWage || 0) > 0) && (
<InlineAlert type="warning" title="本月补扣上月递延">
{batch.entries.filter((e: any) => (e.prevDeferredSocialEmp || 0) + (e.prevDeferredHousingEmp || 0) + (e.prevDeferredMinWage || 0) > 0).length} //
</InlineAlert>
)}
{batch.entries.some((e: any) => e.baseSalary === 0 && e.bonus === 0 && e.totalPay === 0) && (
<InlineAlert type="warning" title="存在全零记录">
0