feat: 付款申请创建采购付款分栏与财务审核权限、工资表银行账号与卡号识别银行、采购台账移除采购油品
已修改4个文件
270 ■■■■■ 文件已修改
src/views/financialManagement/payable/paymentApply.vue 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/monthlyStatistics/components/formDia.vue 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/monthlyStatistics/index.vue 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/procurementManagement/procurementLedger/index.vue 26 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/financialManagement/payable/paymentApply.vue
@@ -73,7 +73,7 @@
        <template #operation="{ row }">
          <el-button type="primary" link @click="view(row)">查看</el-button>
          <el-button type="primary" link @click="edit(row)" v-if="isPendingStatus(row.status)">编辑</el-button>
          <el-button type="success" link @click="handleAudit(row)" v-if="isPendingStatus(row.status)">审核</el-button>
          <el-button type="success" link :disabled="!canAudit(row)" @click="handleAudit(row)" v-if="isPendingStatus(row.status)">审核</el-button>
          <el-button type="warning" link @click="openPaymentDialog(row)" v-if="isApprovedStatus(row.status)">付款</el-button>
          <el-button type="danger" link @click="handleDelete(row)" v-if="isPendingStatus(row.status)">删除</el-button>
        </template>
@@ -213,6 +213,52 @@
        <el-form-item label="备注" prop="remark">
          <el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" :disabled="isView" />
        </el-form-item>
        <div class="form-section-title">创建采购付款</div>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="款项用途" prop="paymentPurpose">
              <el-select v-model="form.paymentPurpose" placeholder="请选择款项用途" clearable style="width: 100%" :disabled="isView">
                <el-option label="采购货款" value="采购货款" />
                <el-option label="运费" value="运费" />
                <el-option label="押金" value="押金" />
                <el-option label="服务费" value="服务费" />
                <el-option label="其他" value="其他" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="项目名称" prop="projectName">
              <el-input v-model="form.projectName" placeholder="请输入项目名称" clearable :disabled="isView" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="经办人" prop="operatorName">
              <el-input v-model="form.operatorName" placeholder="请输入经办人" clearable :disabled="isView" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="财务审核" prop="financeAuditorId">
              <el-select
                v-model="form.financeAuditorId"
                placeholder="请选择审核人员"
                clearable
                filterable
                style="width: 100%"
                :disabled="isView"
                @change="handleFinanceAuditorChange"
              >
                <el-option
                  v-for="item in userList"
                  :key="item.userId"
                  :label="item.nickName"
                  :value="item.userId"
                />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template v-if="!isView" #footer>
        <el-button type="primary" :loading="submitLoading" @click="submitForm">确定</el-button>
@@ -368,6 +414,8 @@
  deleteAccountPaymentApplication,
} from "@/api/financialManagement/accountPaymentApplication.js";
import { addAccountPurchasePayment } from "@/api/financialManagement/accountPurchasePayment.js";
import { userListNoPageByTenantId } from "@/api/system/user.js";
import useUserStore from "@/store/modules/user";
defineOptions({
  name: "付款申请",
@@ -375,6 +423,7 @@
const { proxy } = getCurrentInstance();
const { checkout_payment } = proxy.useDict("checkout_payment");
const userStore = useUserStore();
const filters = reactive({
  invoiceApplicationNo: "",
@@ -395,6 +444,8 @@
  { label: "付款金额", prop: "amount", dataType: "slot", slot: "amount" },
  { label: "付款方式", prop: "paymentMethod", dataType: "slot", slot: "paymentMethod", width: "120" },
  { label: "申请日期", prop: "applyDate", width: "120" },
  { label: "款项用途", prop: "paymentPurpose", width: "120" },
  { label: "财务审核", prop: "financeAuditor", width: "120" },
  { label: "状态", prop: "status", dataType: "slot", slot: "status", width: "100" },
  { label: "操作", prop: "operation", dataType: "slot", slot: "operation", width: "260", fixed: "right" },
];
@@ -409,6 +460,7 @@
const submitLoading = ref(false);
const currentId = ref(null);
const supplierList = ref([]);
const userList = ref([]);
const inboundBatchList = ref([]);
const inboundBatchOptions = ref([]);
@@ -457,6 +509,11 @@
  inboundBatches: "",
  status: 0,
  createTime: "",
  paymentPurpose: "",
  projectName: "",
  operatorName: "",
  financeAuditor: "",
  financeAuditorId: null,
});
const formCreateTimeDate = computed({
  get: () => (form.createTime ? String(form.createTime).split(" ")[0] : ""),
@@ -477,6 +534,10 @@
  paymentAmount: [{ required: true, message: "请输入付款金额", trigger: "blur" }],
  paymentMethod: [{ required: true, message: "请选择付款方式", trigger: "change" }],
  applyDate: [{ required: true, message: "请选择申请日期", trigger: "change" }],
  paymentPurpose: [{ required: true, message: "请选择款项用途", trigger: "change" }],
  projectName: [{ required: true, message: "请输入项目名称", trigger: "blur" }],
  operatorName: [{ required: true, message: "请输入经办人", trigger: "blur" }],
  financeAuditorId: [{ required: true, message: "请选择财务审核人员", trigger: "change" }],
};
const getSummaries = ({ columns, data }) => {
@@ -509,6 +570,15 @@
};
const isPendingStatus = (status) => normalizeStatus(status) === 0;
// 审核权限:指定了财务审核人时仅本人可审核;未指定时保持原逻辑(谁都能审)
const canAudit = (row) => {
  const auditorId = row.financeAuditorId;
  if (auditorId == null || auditorId === "") {
    return true;
  }
  return Number(userStore.id) === Number(auditorId);
};
const isApprovedStatus = (status) => normalizeStatus(status) === 1;
@@ -610,6 +680,11 @@
const fillFormFromRow = (row) => {
  const stockInRecordIds = parseStockInRecordIds(row.stockInRecordIds);
  // 审核人已不在用户列表时,用已保存的姓名补进下拉,保证查看/编辑显示名字而非裸ID
  const auditorId = row.financeAuditorId ?? null;
  if (auditorId != null && !userList.value.some((u) => u.userId === auditorId)) {
    userList.value.push({ userId: auditorId, nickName: row.financeAuditor ?? `用户${auditorId}` });
  }
  Object.assign(form, {
    invoiceApplicationNo: row.invoiceApplicationNo ?? row.applyCode ?? "",
    supplierId: row.supplierId,
@@ -622,6 +697,11 @@
    inboundBatches: formatInboundBatches(row.inboundBatches),
    status: normalizeStatus(row.status),
    createTime: row.createTime ?? "",
    paymentPurpose: row.paymentPurpose ?? "",
    projectName: row.projectName ?? "",
    operatorName: row.operatorName ?? "",
    financeAuditor: row.financeAuditor ?? "",
    financeAuditorId: row.financeAuditorId ?? null,
  });
};
@@ -654,6 +734,11 @@
    status: 0,
    paymentAmount: form.paymentAmount,
    createTime: form.createTime,
    paymentPurpose: form.paymentPurpose || "",
    projectName: form.projectName || "",
    operatorName: form.operatorName || "",
    financeAuditor: form.financeAuditor || "",
    financeAuditorId: form.financeAuditorId ?? null,
  };
  if (forUpdate) {
    payload.id = currentId.value;
@@ -666,6 +751,18 @@
    if (res.code === 200) {
      supplierList.value = res.data ?? [];
    }
  });
};
// 财务审核人员选择:绑定系统用户,选中后带出姓名
const handleFinanceAuditorChange = (userId) => {
  const user = userList.value.find((item) => item.userId === userId);
  form.financeAuditor = user?.nickName ?? "";
};
const getUserList = () => {
  userListNoPageByTenantId().then((res) => {
    userList.value = res.data ?? [];
  });
};
@@ -765,6 +862,11 @@
    inboundBatches: "",
    status: 0,
    createTime: dayjs().format("YYYY-MM-DD HH:mm:ss"),
    paymentPurpose: "",
    projectName: "",
    operatorName: "",
    financeAuditor: "",
    financeAuditorId: null,
  });
  inboundBatchList.value = [];
  inboundBatchOptions.value = [];
@@ -1058,11 +1160,23 @@
onMounted(() => {
  getSupplierList();
  getUserList();
  getTableData();
});
</script>
<style lang="scss" scoped>
.form-section-title {
  margin: 18px 0 14px;
  padding: 8px 14px;
  font-size: 15px;
  font-weight: 600;
  color: #1d6fd8;
  background: linear-gradient(90deg, rgba(64, 158, 255, 0.14), rgba(64, 158, 255, 0.02));
  border-left: 4px solid #409eff;
  border-radius: 3px;
}
.actions {
  display: flex;
  justify-content: space-between;
src/views/personnelManagement/monthlyStatistics/components/formDia.vue
@@ -104,6 +104,17 @@
                </el-select>
              </el-form-item>
            </el-col>
            <el-col :span="6">
              <el-form-item label="银行账号"
                            prop="bankAccount">
                <el-input v-model="form.bankAccount"
                          placeholder="请输入16-19位银行账号"
                          maxlength="19"
                          clearable
                          @input="handleBankAccountInput"
                          @blur="handleBankAccountBlur" />
              </el-form-item>
            </el-col>
          </el-row>
        </el-form>
      </el-card>
@@ -512,6 +523,98 @@
    requestEmployeeRecalculate(row);
  };
  // 银行账号输入:只允许数字,自动剔除空格/横线等,最多19位;输入到16-19位立即自动识别银行
  const handleBankAccountInput = () => {
    form.value.bankAccount = String(form.value.bankAccount ?? "")
      .replace(/\D/g, "")
      .slice(0, 19);
    autoFillPayBank();
  };
  // 卡号识别银行并回填"支付银行"下拉(识别不到不处理,不影响提交)
  const autoFillPayBank = () => {
    const value = form.value.bankAccount;
    if (!/^\d{16,19}$/.test(value)) {
      return;
    }
    const bankName = recognizeBankByCardNo(value);
    if (!bankName) {
      return;
    }
    if (bankOptions.value.includes(bankName)) {
      if (form.value.payBank !== bankName) {
        form.value.payBank = bankName;
        proxy.$modal.msgSuccess(`已自动识别为:${bankName}`);
      }
    } else {
      proxy.$modal.msg(`识别到银行卡属于【${bankName}】,请手动选择支付银行`);
    }
  };
  // 银行账号 Luhn 校验(仅用于警告提示,不阻断提交)
  const luhnValid = account => {
    let sum = 0;
    let alternate = false;
    for (let i = account.length - 1; i >= 0; i--) {
      let n = Number(account[i]);
      if (alternate) {
        n *= 2;
        if (n > 9) n -= 9;
      }
      sum += n;
      alternate = !alternate;
    }
    return sum % 10 === 0;
  };
  // 常见银行借记卡 BIN 前缀表(前6位,仅用于自动识别,不做强校验)
  const BANK_BINS = {
    "中国工商银行": ["620200", "621210", "621211", "621225", "621226", "621227", "621288", "621723", "622202", "622203"],
    "中国建设银行": ["621284", "621466", "621488", "621499", "621598", "621599", "621700", "622700"],
    "中国农业银行": ["621282", "621336", "622827", "622828", "622845", "622848", "622849"],
    "中国银行": ["621660", "621661", "621662", "621663", "621665", "621666", "621667", "621668", "621669", "621725", "621756", "621790", "622760"],
    "交通银行": ["622258", "622260", "622261", "622262"],
    "招商银行": ["621286", "621483", "621485", "621486", "622580", "622588"],
    "中国邮政储蓄银行": ["621095", "621799", "622150", "622151", "622188"],
    "兴业银行": ["622908", "622909"],
    "中国民生银行": ["621691", "622615", "622617", "622622"],
    "中国光大银行": ["622661", "622662", "622663"],
    "中信银行": ["621771", "621773", "622690", "622698"],
    "上海浦东发展银行": ["621792", "621793", "621795", "622521", "622522"],
    "华夏银行": ["622630", "622631", "622632"],
    "平安银行": ["621625", "621626", "622155", "622156"],
    "广发银行": ["622568", "622569"],
  };
  // 根据卡号前6位识别银行名称(识别不到返回空串)
  const recognizeBankByCardNo = account => {
    if (!/^\d{16,19}$/.test(account)) {
      return "";
    }
    const prefix6 = account.slice(0, 6);
    for (const [bank, prefixes] of Object.entries(BANK_BINS)) {
      if (prefixes.includes(prefix6)) {
        return bank;
      }
    }
    return "";
  };
  // 失焦校验:长度/格式错误由表单规则红色提示并拦截;Luhn 不通过仅黄色警告;再识别一次银行
  const handleBankAccountBlur = () => {
    const value = form.value.bankAccount;
    if (!value) {
      return;
    }
    if (!/^\d{16,19}$/.test(value)) {
      return; // 红色错误由表单规则处理
    }
    if (!luhnValid(value)) {
      proxy.$modal.msgWarning("银行账号 Luhn 校验未通过(仅提示,可继续提交)");
    }
    autoFillPayBank();
  };
  // 基础资料表单
  const data = reactive({
    form: {
@@ -522,6 +625,7 @@
      remark: "",
      payBank: "",
      auditUserId: undefined,
      bankAccount: "",
    },
    rules: {
      salaryTitle: [
@@ -533,6 +637,20 @@
      ],
      auditUserId: [
        { required: true, message: "请选择审核人", trigger: "change" },
      ],
      bankAccount: [
        {
          validator: (rule, value, callback) => {
            if (!value) {
              return callback();
            }
            if (!/^\d{16,19}$/.test(value)) {
              return callback(new Error("银行账号必须为16-19位纯数字"));
            }
            callback();
          },
          trigger: "blur",
        },
      ],
    },
  });
@@ -656,6 +774,7 @@
        remark: "",
        payBank: "",
        auditUserId: undefined,
        bankAccount: "",
      });
      // 编辑:列表页已返回主表字段;这里只做回显(明细由“生成工资表/计算工资”得到)
      if (type === "edit" && row?.id) {
@@ -672,6 +791,7 @@
        form.value.remark = row.remark ?? "";
        form.value.payBank = row.payBank ?? "";
        form.value.auditUserId = row.auditUserId ?? undefined;
        form.value.bankAccount = row.bankAccount ?? "";
        // 如果有员工明细数据,直接反显
        if (row.staffSalaryDetailList && row.staffSalaryDetailList.length > 0) {
@@ -809,6 +929,12 @@
  };
  const saveData = status => {
    // 提交前兜底校验银行账号:长度/格式不符直接拦截
    const account = String(form.value.bankAccount ?? "");
    if (account && !/^\d{16,19}$/.test(account)) {
      proxy.$modal.msgError("银行账号必须为16-19位纯数字,请修改后再提交");
      return;
    }
    const payload = {
      id: form.value.id,
      salaryTitle: form.value.salaryTitle,
@@ -818,6 +944,7 @@
      payBank: form.value.payBank,
      auditUserId: form.value.auditUserId,
      auditUserName: auditUserName.value,
      bankAccount: account,
      totalSalary: totalSalary.value,
      staffSalaryDetailList: employeeList.value.map(e => ({
        staffOnJobId: e.staffOnJobId ?? e.staffId ?? e.id,
src/views/personnelManagement/monthlyStatistics/index.vue
@@ -149,6 +149,7 @@
  },
  { label: "工资总额", prop: "totalSalary", width: 120 },
  { label: "支付银行", prop: "payBank", width: 120 },
  { label: "银行账号", prop: "bankAccount", width: 180 },
  { label: "审核人", prop: "auditUserName", width: 110 },
  { label: "备注", prop: "remark", minWidth: 120 },
  {
src/views/procurementManagement/procurementLedger/index.vue
@@ -424,24 +424,6 @@
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="采购油品:"
                          prop="purchaseOilProduct">
              <el-select v-model="form.purchaseOilProduct"
                         placeholder="请选择"
                         clearable
                         style="width: 100%">
                <el-option label="汽#92"
                           value="汽#92" />
                <el-option label="汽#95"
                           value="汽#95" />
                <el-option label="柴#0"
                           value="柴#0" />
                <el-option label="柴#-35"
                           value="柴#-35" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="物流方式:"
                          prop="logisticsMethod">
              <el-select v-model="form.logisticsMethod"
@@ -457,8 +439,6 @@
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="提货方式:"
                          prop="pickupMethod">
@@ -468,6 +448,8 @@
              </el-radio-group>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="采购油库:"
                          prop="oilDepotId">
@@ -491,8 +473,6 @@
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="客户名称:"
                          prop="customerName">
@@ -502,6 +482,8 @@
                        :disabled="!['代储', '采购直销'].includes(form.purchaseCategory)" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="采购数量(吨):"
                          prop="purchaseQuantity">