Browse Source

feat:采购模块

snow 2 years ago
parent
commit
42c6d04c25

+ 36 - 0
src/hooks/process/use-review-process.ts

@@ -0,0 +1,36 @@
+import { reactive, ref, watchEffect } from "vue";
+import { ElForm, FormRules } from "element-plus";
+
+type Param = {
+  callback: (...args: any[]) => any;
+  failStatus: string;
+};
+
+export function useReviewProcess({ callback, failStatus }: Param) {
+  const formRef = ref<InstanceType<typeof ElForm>>(null);
+
+  const formData = reactive({
+    status: "",
+    remark: ""
+  });
+
+  const formRules = reactive<FormRules>({
+    status: [{ required: true, trigger: "change", message: "请选择审核状态" }],
+    remark: [{ required: true, trigger: "change", message: "请输入备注" }]
+  });
+
+  const handleChangeStatus = () =>
+    formRef.value.validate(isValid => isValid && callback());
+
+  //不通过时备注为必填
+  watchEffect(
+    () => (formRules.remark[0].required = formData.status === failStatus)
+  );
+
+  return {
+    formRef,
+    formRules,
+    formData,
+    handleChangeStatus
+  };
+}

+ 55 - 0
src/views/purchase/inputInvoice/config/configs.ts

@@ -0,0 +1,55 @@
+//发票添加类型的options
+export const invType_options = [
+  {
+    label: "手工添加",
+    value: "1"
+  },
+  {
+    label: "orc识别",
+    value: "2"
+  }
+];
+
+//发票状态的options
+export const status_options = [
+  {
+    label: "上传发票",
+    value: "0"
+  },
+  {
+    label: "待识别验证",
+    value: "1"
+  },
+  {
+    label: "待校验识别",
+    value: "2"
+  },
+  {
+    label: "待财务审核",
+    value: "3"
+  },
+  {
+    label: "认证成功待确认完成",
+    value: "7"
+  },
+  {
+    label: "回票完成",
+    value: "8"
+  },
+  {
+    label: "识别失败",
+    value: "9"
+  },
+  {
+    label: "验证失败",
+    value: "10"
+  },
+  {
+    label: "财务驳回",
+    value: "11"
+  },
+  {
+    label: "认证失败",
+    value: "12"
+  }
+];

+ 50 - 0
src/views/purchase/ticketReturn/components/approval-process/authentication.vue

@@ -0,0 +1,50 @@
+<script setup lang="ts">
+import { useReviewProcess } from "/@/hooks";
+
+const emit = defineEmits(["changeStatus"]);
+
+const process_status = {
+  successStatus: "7",
+  failstatus: "12"
+};
+
+const { formRef, formRules, formData, handleChangeStatus } = useReviewProcess({
+  callback: () => emit("changeStatus", formData),
+  failStatus: process_status.failstatus
+});
+</script>
+
+<template>
+  <el-form
+    ref="formRef"
+    :rules="formRules"
+    :model="formData"
+    label-width="100px"
+    justify-between
+    inline
+    flex
+  >
+    <div flex flex-col style="width: 50%">
+      <el-form-item label="审核状态" prop="status">
+        <el-select placeholder="请选择审核状态" v-model="formData.status">
+          <el-option :value="process_status.successStatus" label="通过" />
+          <el-option :value="process_status.failstatus" label="不通过" />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="备注" prop="remark">
+        <el-input
+          type="textarea"
+          :maxlength="2000"
+          :rows="5"
+          placeholder="请输入备注"
+          v-model="formData.remark"
+        />
+      </el-form-item>
+    </div>
+
+    <el-button type="primary" @click="handleChangeStatus"
+      >提交审核结果</el-button
+    >
+  </el-form>
+</template>

+ 49 - 0
src/views/purchase/ticketReturn/components/approval-process/confirm.vue

@@ -0,0 +1,49 @@
+<script setup lang="ts">
+import { useReviewProcess } from "/@/hooks";
+const process_status = {
+  successStatus: "8",
+  failstatus: "12"
+};
+
+const emit = defineEmits(["changeStatus"]);
+
+const { formRef, formRules, formData, handleChangeStatus } = useReviewProcess({
+  callback: () => emit("changeStatus", formData),
+  failStatus: process_status.failstatus
+});
+</script>
+
+<template>
+  <el-form
+    :rules="formRules"
+    :model="formData"
+    ref="formRef"
+    label-width="100px"
+    justify-between
+    inline
+    flex
+  >
+    <div flex flex-col style="width: 50%">
+      <el-form-item label="审核状态" prop="status">
+        <el-select placeholder="请选择审核状态" v-model="formData.status">
+          <el-option :value="process_status.successStatus" label="通过" />
+          <el-option :value="process_status.failstatus" label="不通过" />
+        </el-select>
+      </el-form-item>
+
+      <el-form-item label="备注" prop="remark">
+        <el-input
+          type="textarea"
+          :maxlength="2000"
+          :rows="5"
+          placeholder="请输入备注"
+          v-model="formData.remark"
+        />
+      </el-form-item>
+    </div>
+
+    <el-button type="primary" @click="handleChangeStatus"
+      >提交审核结果</el-button
+    >
+  </el-form>
+</template>

+ 262 - 0
src/views/purchase/ticketReturn/components/create-ticket-dialog/create-ticket-dialog.vue

@@ -0,0 +1,262 @@
+<script setup lang="ts">
+import { dayjs, ElForm, ElInput, ElMessage, UploadProps } from "element-plus";
+import { ticketFormItems, ticketAddRules } from "../../config/configs";
+import { computed, ref, watch, nextTick } from "vue";
+import { httpAdd } from "/@/api/purchase/ticketReturn";
+import { useResponseHandle } from "/@/hooks";
+import { httpImageUpload, baseUrl } from "/@/api/other";
+import { useUserStore } from "/@/store/modules/user";
+import { inv_type_list } from "/@/utils/status";
+
+const visible = ref(false);
+
+//支持的图片格式
+const types = ["png", "jpg", "bmp", "jpeg"].map(format => "image/" + format);
+
+const payNo = ref("");
+const invType = ref("");
+const loading = ref(false);
+const formData = ref<Record<string, string>>({});
+const formRef = ref<InstanceType<typeof ElForm>>(null);
+const formItems = computed(() => ticketFormItems[invType.value] || []);
+const showScanInput = computed(() => invType.value === "1");
+const scanInputValue = ref("");
+const rules = ref({ ...ticketAddRules });
+
+const userStore = useUserStore();
+const responseHandle = useResponseHandle();
+
+//图片上传前判断类型大小尺寸
+const onBeforeReturnImageUpload: UploadProps["beforeUpload"] = ({
+  type,
+  size
+}) => {
+  if (!types.includes(type)) {
+    ElMessage.error("请上传jpg.png.bmp.jpeg类型图片");
+    return false;
+  }
+
+  if (size / 1024 / 1024 > 1) {
+    ElMessage.error("图片大小超过1M");
+    return false;
+  }
+};
+
+//保存采购回票
+function handleSave() {
+  formRef.value.validate(async isValid => {
+    if (!isValid) return;
+    loading.value = true;
+
+    const { code, message } = await httpAdd({
+      payNo: payNo.value,
+      invType: invType.value,
+      ...formData.value
+    });
+
+    responseHandle({
+      code,
+      message,
+      handler: () => (visible.value = false)
+    });
+
+    loading.value = false;
+  });
+}
+
+function handleClose() {
+  formData.value = {};
+  invType.value = "";
+  scanInputValue.value = "";
+}
+
+function handleScanKeydown(evt: KeyboardEvent) {
+  const { keyCode } = evt;
+
+  if (keyCode !== 13) return;
+
+  //校验扫描枪值是否含有中文逗号
+  if (scanInputValue.value.indexOf(",") >= 0) {
+    ElMessage.error("不能包含中文逗号");
+    return (scanInputValue.value = "");
+  }
+
+  const [_, ...chunks] = scanInputValue.value.split(",");
+  const [invoiceType, invCode, invNumber, gold, open_time, checkNumber] =
+    chunks;
+
+  const year = open_time.slice(0, 4);
+  const month = open_time.slice(4, 6);
+  const day = open_time.slice(6);
+
+  formData.value = {
+    invoiceType,
+    invCode,
+    gold,
+    open_time: dayjs(`${year}-${month}-${day}`).format("YYYY-MM-DD HH:mm:ss"),
+    invNumber,
+    checkNumber
+  };
+  console.log(formData.value);
+  scanInputValue.value = "";
+}
+
+const handleRequeset: UploadProps["httpRequest"] = async ({ file }) => {
+  const _formData = new FormData();
+  _formData.append("img", file);
+  _formData.append("token", userStore.token);
+  const { message, code, data } = await httpImageUpload(_formData);
+
+  responseHandle({
+    message,
+    code,
+    handler: () => {
+      const { url, name } = data[0];
+      formData.value.inv_img = baseUrl + "/" + url;
+      formData.value.invName = name;
+    }
+  });
+};
+
+watch(
+  () => formItems.value,
+  newVal => {
+    const newFormData: Record<string, string> = {};
+    newVal.forEach(item => (newFormData[item.prop] = ""));
+    formData.value = newFormData;
+  }
+);
+
+//增值税专票校验码不必填
+watch(
+  () => formData.value,
+  () => {
+    nextTick(
+      () =>
+        (rules.value.checkNumber[0].required = formData.value.invName !== "01")
+    );
+  },
+  {
+    deep: true
+  }
+);
+
+defineExpose({
+  onDisplay: (_payNo: string) => {
+    visible.value = true;
+    payNo.value = _payNo;
+  }
+});
+</script>
+
+<template>
+  <el-dialog
+    v-model="visible"
+    custom-class="ticket__dialog"
+    title="添加发票"
+    center
+    destroy-on-close
+    @close="handleClose"
+  >
+    <el-form label-width="100px" :model="formData" :rules="rules" ref="formRef">
+      <el-form-item label="申请类型">
+        <el-select v-model="invType">
+          <el-option label="手工添加" value="1" />
+          <el-option label="ocr识别" value="2" />
+        </el-select>
+      </el-form-item>
+
+      <!-- 扫码枪识别 -->
+      <el-form-item label="扫码枪" v-if="showScanInput">
+        <el-input
+          type="textarea"
+          @keydown="handleScanKeydown"
+          v-model="scanInputValue"
+        />
+      </el-form-item>
+
+      <el-form-item
+        v-for="(item, index) in formItems"
+        :label="item.label"
+        :prop="item.prop"
+        :key="index"
+      >
+        <template v-if="item.prop === 'invName'">
+          <el-select v-model="formData[item.prop]" placeholder="请选择发票名称">
+            <el-option
+              v-for="(type, index) in inv_type_list"
+              :label="type.label"
+              :value="type.hwy_value"
+              :key="index"
+            />
+          </el-select>
+        </template>
+
+        <template v-else-if="item.prop === 'inv_img'">
+          <div>
+            <el-upload
+              class="upload"
+              :before-upload="onBeforeReturnImageUpload"
+              :http-request="handleRequeset"
+              :show-file-list="false"
+            >
+              <img
+                class="avatar"
+                :src="formData[item.prop]"
+                v-if="formData[item.prop]"
+              />
+              <div class="text" v-else>点击上传</div>
+            </el-upload>
+            <span>大小:小于1M; 尺寸:100*100; 类型:jpg.png.bmp.jpeg</span>
+          </div>
+        </template>
+
+        <template v-else-if="item.prop === 'open_time'">
+          <el-date-picker
+            v-model="formData[item.prop]"
+            value-format="YYYY-MM-DD HH:mm:ss"
+            placeholder="请输入开票日期"
+          />
+        </template>
+
+        <template v-else>
+          <el-input v-model="formData[item.prop]" />
+        </template>
+      </el-form-item>
+
+      <el-form-item justify-end flex w-full>
+        <el-button type="primary" :loading="loading" @click="handleSave"
+          >保存</el-button
+        >
+      </el-form-item>
+    </el-form>
+  </el-dialog>
+</template>
+
+<style lang="scss" scoped>
+.upload {
+  width: 178px;
+  height: 178px;
+  display: block;
+  border: 1px dashed var(--el-border-color);
+
+  .icon {
+    font-size: 24px;
+    color: #8c939d;
+    width: 178px;
+    height: 178px;
+    text-align: center;
+  }
+
+  .text {
+    font-size: 20px;
+    line-height: 178px;
+    width: 178px;
+    text-align: center;
+  }
+
+  &:hover {
+    border-color: #409eff;
+  }
+}
+</style>

+ 25 - 0
src/views/purchase/ticketReturn/components/create-ticket-dialog/index.vue

@@ -0,0 +1,25 @@
+<script setup lang="ts">
+import { ref } from "vue";
+import PaymentTable from "./payment-table.vue";
+import CreateTicketDialog from "./create-ticket-dialog.vue";
+
+const visible = ref(false);
+
+const ticketDialogRef = ref<InstanceType<typeof CreateTicketDialog>>(null);
+
+function handleAdd({ payNo }) {
+  ticketDialogRef.value.onDisplay(payNo);
+}
+
+defineExpose({
+  onDisplay: () => (visible.value = true)
+});
+</script>
+
+<template>
+  <el-dialog v-model="visible" title="新建采购回票" center destroy-on-close>
+    <PaymentTable @add-btn-click="handleAdd" />
+
+    <CreateTicketDialog ref="ticketDialogRef" />
+  </el-dialog>
+</template>

+ 53 - 0
src/views/purchase/ticketReturn/components/create-ticket-dialog/payment-table.vue

@@ -0,0 +1,53 @@
+<script setup lang="ts">
+import { onMounted, ref } from "vue";
+import { purchase_columns } from "../../config/configs";
+import { ElTable } from "element-plus";
+import { useResponseHandle } from "/@/hooks";
+import { httpList } from "/@/api/purchase/orderRecord";
+
+const emit = defineEmits(["addBtnClick"]);
+const tableRef = ref<InstanceType<typeof ElTable>>(null);
+const loading = ref(false);
+const purchaseList = ref([]);
+const responseHandle = useResponseHandle();
+
+async function requestPurchaseList() {
+  loading.value = true;
+  const { code, message, data } = await httpList({});
+
+  responseHandle({
+    code,
+    message,
+    handler: () => (purchaseList.value = data.list)
+  });
+  loading.value = false;
+}
+
+onMounted(() => requestPurchaseList());
+</script>
+
+<template>
+  <el-table
+    border
+    v-loading="loading"
+    ref="tableRef"
+    row-key="id"
+    size="small"
+    :data="purchaseList"
+  >
+    <el-table-column
+      v-for="(col, index) in purchase_columns"
+      :key="index"
+      v-bind="col"
+      show-overflow-tooltip
+    />
+
+    <el-table-column fixed="right" label="操作">
+      <template #="{ row }">
+        <el-button link type="primary" @click="emit('addBtnClick', row)"
+          >添加</el-button
+        >
+      </template>
+    </el-table-column>
+  </el-table>
+</template>