snow 8 maanden geleden
bovenliggende
commit
3e95ba8f9d

+ 0 - 2
.env.development

@@ -28,10 +28,8 @@ VITE_IS_PROD = true
 VITE_PURCHASE_URL = 'http://pin.caixiao365.com/'
 # 跳转工单地址
 VITE_GD_URL = 'http://bug.caixiao365.com/'
-
 # 报表地址
 VITE_DASHBOARD_URL = 'http://web.report.caixiao365.com/'
-
 # token密钥
 VITE_SECRET_KEY = 'key123'
 

+ 6 - 0
src/api/InvoiceSales/invoiceApply/index.ts

@@ -63,3 +63,9 @@ export const httpBatchApproval = (data: object): ResponseType => {
 export const httpBack = (data: object): any => {
   return http.request("post", `${yewuApi}ticketedit`, { data });
 };
+
+
+
+export const httpBatchUploadInvoice = (data: object): any => {
+  return http.request("post", `${yewuApi}orderInv/importInvByWanyu`, { data });
+};

+ 0 - 6
src/components/PageContainer/src/page-container.tsx

@@ -12,8 +12,6 @@ import {
   type PageContentInstance
 } from "/@/components/PageContent";
 
-import { Notice } from "../../Notice"
-
 const PageContainer = defineComponent({
   name: "PageContainer",
   props: {
@@ -146,10 +144,6 @@ const PageContainer = defineComponent({
 
 
     const list = ['系统需求和问题,请点击右上角工单填写,工程师会逐一回复并处理。','系统需求和问题,请点击右上角工单填写,工程师会逐一回复并处理。']
-<<<<<<< HEAD
-
-=======
->>>>>>> v3.0
     return () => {
       return (
         <div class="w-full h-full" v-loading={loading.value}>

+ 4 - 2
src/components/execlUpload/src/execl-upload.vue

@@ -1,5 +1,7 @@
 <script setup lang="ts">
-import { ref, reactive } from "vue";
+import { ref } from "vue";
+import { read, utils } from "xlsx";
+
 import {
   ElMessage,
   ElUpload,
@@ -7,7 +9,6 @@ import {
   UploadProps,
   ElLoading
 } from "element-plus";
-import { read, utils } from "xlsx";
 
 const uploadRef = ref<InstanceType<typeof ElUpload>>(null);
 const execlFile = ref<UploadFile | null>(null);
@@ -73,6 +74,7 @@ const beforeUpload: UploadProps["beforeUpload"] = async uploadFile => {
   ElMessage.warning("请不要上传大于500KB的文件");
   return false;
 };
+
 //删除穿文件
 const handleRemove: UploadProps["onRemove"] = () => (execlFile.value = null);
 

+ 1 - 0
src/hooks/core/index.ts

@@ -1,2 +1,3 @@
 export { useRender } from "./useRender"
 export { useAsync } from "./useAsync"
+export { useTask, useTasks } from "./useTask"

+ 69 - 0
src/hooks/core/useTask.ts

@@ -0,0 +1,69 @@
+import { ref, reactive, unref , computed} from "vue";
+import { useNav } from "/@/layout/hooks/nav";
+import { ElMessage } from "element-plus";
+import { IDataType } from "/@/api/types";
+
+type Params = {
+  code: number;
+  message: string;
+  handler: () => void;
+};
+
+export function useResponseHandle() {
+  const { logout } = useNav();
+  function responseHandle({ code, message, handler }: Params) {
+    const c = Number(code);
+    if (c === 0) {
+      handler();
+    } else if (c >= 100 && c <= 140) {
+      logout();
+    } else {
+      ElMessage.error(message);
+    }
+  }
+  return responseHandle;
+}
+
+
+interface TaskOptions<D>{
+  root?: boolean
+  initialData?: D
+  success?: (data?:D) => void 
+}
+
+export function useTask<D = any>({ initialData, root = true, success }: TaskOptions<D> = {  root: true } ) {
+  const error = ref(false);
+  const loading = ref(false);
+  const { logout } = useNav();
+  const data = ref<D>(initialData);
+
+  function run(promise: Promise<IDataType<D>>) {
+    loading.value = true
+    return promise.then(response => {
+      loading.value = false
+      let { code, message } = response
+      code = Number(code)
+      if(code === 0){
+        error.value = false
+        data.value = root ? response.data : (response.data as unknown as any).list
+        success && success(unref(response.data));
+      }else if(code >= 100 && code <= 140){
+        logout()
+        error.value = true
+      }else{
+        ElMessage.warning(message)
+        error.value = true
+      }
+    });
+  }
+
+  return reactive({ run, data, error, loading, logout })
+}
+
+
+export function useTasks(...tasks: ReturnType<typeof useTask>[]){
+  return computed(() => ({
+    loading: tasks.some(task => task.loading),
+    error: tasks.some(task => task.error)
+  }))
+}

+ 0 - 4
src/style/index.scss

@@ -199,10 +199,6 @@ body {
 }
 
 
-<<<<<<< HEAD
-=======
-
->>>>>>> v3.0
 .carousel-x-enter-active,
 .carousel-x-leave-active {
   transition: transform 1s linear;

+ 25 - 47
src/views/InvoiceSales/invoiceDetailImport/index.vue

@@ -1,71 +1,49 @@
 <script setup lang="ts">
 import { ref } from "vue";
-import { httpUpload } from "/@/api/InvoiceSales/invoiceApply";
-import { responseHandle } from "/@/utils/responseHandle";
+import { httpBatchUploadInvoice } from "/@/api/InvoiceSales/invoiceApply";
 import { execlUpload } from "/@/components/execlUpload";
-import { useNav } from "/@/layout/hooks/nav";
+import { useTask } from "/@/hooks/core"
 import { ElMessage } from "element-plus";
-import dayjs from "dayjs";
 
 
-import { columns, isEmpty, isHeaderSame, generateTableData } from "./utils";
-
-
-const loading = ref(false);
-const tableData = ref([]);
-const { logout } = useNav();
+import { columns,isEmpty, isHeaderSame, generateTableData, validate, translateData } from "./utils";
 const emit = defineEmits(["onSuccess"]);
+const tableData = ref<Record<string,any>[]>([])
 
-const Uploadsuccess = ({ results, header }) => {
-  loading.value = true;
+const uploadTask = useTask({ success(){
+  tableData.value = []
+  ElMessage.success("数据导入成功!");
+  emit("onSuccess");
+} })
 
+const Uploadsuccess = ({ results, header }) => {
+  uploadTask.loading = true;
   if (isEmpty(results)) {
     ElMessage.error("表格无有效数据!");
-    loading.value = false;
+    uploadTask.loading = false;
     return;
   }
-
-  if(isHeaderSame(header)){
+  if(!isHeaderSame(header)){
     ElMessage.error("表头与导入模板不匹配!");
-    loading.value = false;
+    uploadTask.loading = false;
     return;
-
   }
-
-  tableData.value = generateTableData(results)
-  loading.value = false;
+  const data = generateTableData(results)
+  if(validate(data)){ tableData.value = data }
+  uploadTask.loading = false;
 };
 
-
-//提交
-const handleSubmit = async () => {
-  if (loading.value) return;
-  loading.value = true;
-
-  const data = ""
-
-  const { code, message } = await httpUpload({ data });
-  loading.value = false;
-  responseHandle({
-    code,
-    message,
-    logout,
-    handler: () => {
-      ElMessage.success("数据导入成功!");
-      emit("onSuccess");
-    }
-  });
-};
-
-const cancel = () => {
-  tableData.value = [];
-};
+function onSubmit() { 
+  if (uploadTask.loading) return;
+  if(!validate(tableData.value)){ return }
+  const data = translateData(tableData.value)
+  uploadTask.run(httpBatchUploadInvoice({ data }))
+}
 </script>
 
 <template>
   <div class="flex flex-col bg-white">
     <execlUpload class="mb-[10px]" @on-success="Uploadsuccess" v-if="tableData.length === 0" />
-
     <el-table
       :data="tableData"
       stripe
@@ -90,8 +68,8 @@ const cancel = () => {
       v-if="tableData.length !== 0"
       style="padding: 10px 0 0 0"
     >
-      <el-button size="small" @click="cancel">取消</el-button>
-      <el-button size="small" type="primary" :loading="loading" @click="handleSubmit">保存</el-button
+      <el-button size="small" @click="tableData = []">取消</el-button>
+      <el-button size="small" type="primary" :loading="uploadTask.loading" @click="onSubmit">保存</el-button
       >
     </div>
   </div>

+ 133 - 40
src/views/InvoiceSales/invoiceDetailImport/utils.ts

@@ -1,29 +1,33 @@
+import { ElMessage } from "element-plus";
+
 export const initheaders = [
-  { label : "序号", width: '160px', prop:'invNo' },
-  { label : "发票代码",  width: '120px', prop: 'inv_code' },
-  { label : "发票号码",  width: '120px', prop:'inv_number' },
-  { label : "数电票号码",  width: '160px', prop: 'inv_number_electionic' },
-  { label : "销方识别号",  width: '160px', prop: 'seller_id' },
-  { label : "销方名称",  width: '120px', prop: 'seller_title' },
-  { label : "购方识别号",  width: '140px', prop: 'buyer_id' },
-  { label : "购买方名称",  width: '140px', prop: 'buyer_title' },
-  { label : "开票日期",  width: '140px', prop: 'open_date' },
-  { label : "货物或应税劳务名称",  width: '140px', prop: 'XMMC' },
-  { label : "规格型号",  width: '140px', prop: 'GGXH' },
-  { label : "单位",  width: '140px', prop: 'DW' },
-  { label : "数量",  width: '140px', prop: 'SPSL' },
-  { label : "单价",  width: '140px', prop: 'DJ' },
-  { label : "金额",  width: '140px', prop: 'JE' },
-  { label : "税率",  width: '140px', prop: 'SL' },
-  { label : "税额",  width: '140px', prop: 'SE' },
-  { label : "价税合计",  width: '140px', prop: 'JSHJ' },
-  { label : "发票来源",  width: '140px', prop: 'source' },
-  { label : "发票票种",  width: '140px', prop: 'inv_type' },
-  { label : "发票状态",  width: '140px', prop: 'status' },
-  { label : "是否正数发票",  width: '140px', prop: 'type' },
-  { label : "发票风险等级",  width: '140px', prop: 'warning' },
-  { label : "开票人",  width: '140px', prop: 'person' },
-  { label : "备注",  width: '140px', prop: 'remark' },
+  { label : "序号", width: '160px', prop:'invNo', templateValue: '' },
+  { label : "发票代码",  width: '120px', prop: 'inv_code', templateValue: '' },
+  { label : "发票号码",  width: '120px', prop:'inv_number', templateValue: '' },
+  { label : "数电票号码",  width: '160px', prop: 'inv_number_electionic', templateValue: '' },
+  { label : "销方识别号",  width: '160px', prop: 'seller_id', templateValue: '' },
+  { label : "销方名称",  width: '120px', prop: 'seller_title', templateValue: '' },
+  { label : "购方识别号",  width: '140px', prop: 'buyer_id' , templateValue: ''},
+  { label : "购买方名称",  width: '140px', prop: 'buyer_title', templateValue: '' },
+  { label : "开票日期",  width: '140px', prop: 'open_date' , templateValue: ''},
+  { label : "税收分类编码",  width: '140px', prop: 'SSFL' , templateValue: ''},
+  { label : "特定业务类型",  width: '140px', prop: 'TDYWLX' , templateValue: ''},
+  { label : "货物或应税劳务名称",  width: '140px', prop: 'XMMC' , templateValue: ''},
+  { label : "规格型号",  width: '140px', prop: 'GGXH', templateValue: '' },
+  { label : "单位",  width: '140px', prop: 'DW', templateValue: '' },
+  { label : "数量",  width: '140px', prop: 'SPSL', templateValue: '' },
+  { label : "单价",  width: '140px', prop: 'DJ', templateValue: '' },
+  { label : "金额",  width: '140px', prop: 'JE', templateValue: '' },
+  { label : "税率",  width: '140px', prop: 'SL', templateValue: '' },
+  { label : "税额",  width: '140px', prop: 'SE', templateValue: '' },
+  { label : "价税合计",  width: '140px', prop: 'JSHJ', templateValue: '' },
+  { label : "发票来源",  width: '140px', prop: 'source', templateValue: '' },
+  { label : "发票票种",  width: '140px', prop: 'inv_type', templateValue: '' },
+  { label : "发票状态",  width: '140px', prop: 'status', templateValue: '' },
+  { label : "是否正数发票",  width: '140px', prop: 'type', templateValue: '' },
+  { label : "发票风险等级",  width: '140px', prop: 'warning',templateValue: '' },
+  { label : "开票人",  width: '140px', prop: 'person', templateValue: '' },
+  { label : "备注",  width: '140px', prop: 'remark', templateValue: '' },
 ];
 
 export const mapLabel2Field = initheaders.reduce((previous, current) => ({
@@ -31,30 +35,24 @@ export const mapLabel2Field = initheaders.reduce((previous, current) => ({
   [current.label]: current.prop
 }), {})
 
-console.log(mapLabel2Field)
+export const columns = [{ type: "index", width: "50", fixed: "left", label: "序号" }, ...initheaders]
+export const template = [initheaders.reduce((prev,current) => ({ ...prev, [current.label]: current.templateValue }), {})]
 
-export const columns = [
-  { type: "index", width: "50", fixed: "left", label: "序号" },
-  ...initheaders
-]
 
 // 校验数据是否为空
 export const isEmpty = (results:any[]) => results.length === 0
-
 // 校验导入模板表头是否一致
 export function isHeaderSame(importHeader: any[]){
-  if(importHeader.length !== initheaders.length){
-    return false
-  }
-
+  if(importHeader.length !== initheaders.length){ return false }
   for(const index in importHeader){
-    if(initheaders[index] !== importHeader[index]){ return false }
+    if(initheaders[index].label !== importHeader[index]){ 
+      console.log(initheaders[index], importHeader[index])
+      return false 
+    } 
   }
-
   return true
 }
 
-
 // 生成导入数据
 export function generateTableData(results: any[]){
   const tableData = []
@@ -67,7 +65,102 @@ export function generateTableData(results: any[]){
     }
     tableData.push(tableItem)
   }
-
-  console.log(tableData)
   return tableData
 }
+
+
+const normalInvoiceTypes = [
+  { label: "增值税专用发票", value: "special" },
+  { label: "增值税普通发票", value: "normal"  },
+  { label: "增值税电子专用发票", value: "special_electronic" },
+  { label: "增值税电子普通发票", value: "electronic" }
+];
+
+
+const fullyInvoiceTypes = [
+  { label: "数电票(增值税专用发票)", value: "fully_digitalized_special_electronic" },
+  { label: "数电票(普通发票)", value: "fully_digitalized_normal_electronic" },
+]
+
+
+const fullyInvoiceLabels = fullyInvoiceTypes.map(({label}) => label)
+const normalInvoiceLabels = normalInvoiceTypes.map(({label}) => label)
+
+const isPositiveNumberOptions = [ { label: '是', value: 1 }, { label: '否', value: 0 } ]
+const isPositiveNumberLabels = isPositiveNumberOptions.map(({ label }) => label)
+
+
+const mapIsPositiveNumber = isPositiveNumberOptions.reduce((prev, current) => ({
+  ...prev,
+  [current.label]: current.value
+}), {})
+
+
+const mapInvoiceTypes = ([...fullyInvoiceTypes, ...normalInvoiceTypes]).reduce((prev, current) => ({
+  ...prev,
+  [current.label]: current.value
+}), {})
+
+export function validate(lines: Record<string, any>[]){
+  for(const index in lines){
+    const row = lines[index]
+    const invoiceType = row['inv_type']
+    const isPositiveNumber = row['type']
+    if(!([...fullyInvoiceLabels, ...normalInvoiceLabels]).includes(invoiceType)){
+      ElMessage.warning(`第${Number(index) + 1}行,发票票种必须为:${([...fullyInvoiceLabels, ...normalInvoiceLabels]).join(',')}`)
+      return false
+    }
+
+
+    if(!isPositiveNumberLabels.includes(isPositiveNumber)){
+      ElMessage.warning(`第${Number(index) + 1}行,是否为正数发票必须为: 是/否`)
+      return false
+    }
+
+
+    if(fullyInvoiceLabels.includes(invoiceType) && (row['inv_number_electionic']).trim() === ''){
+      ElMessage.warning(`第${Number(index) + 1}行,发票票种为数电票是,数电票号码不能为空`)
+      return false
+    }
+
+
+    if(normalInvoiceLabels.includes(invoiceType) && (row['inv_number']).trim() === ''){
+      ElMessage.warning(`第${Number(index) + 1}行,发票票种为非数电票时,发票票号码不能为空`)
+      return false
+    }
+  
+  }
+  return true
+}
+
+export function translateData(data: Record<string, any>[]){
+  const target = []
+  data.forEach(item => {
+
+    const { 
+      inv_number_electionic, 
+      inv_number, 
+      inv_type,
+      type, 
+      ...rest 
+    } = item
+    
+    target.push({
+      inv_number: fullyInvoiceLabels.includes(inv_type) ? inv_number_electionic : inv_number,
+      inv_type: mapInvoiceTypes[inv_type],
+      type: mapIsPositiveNumber[type],
+      check_code: '',
+      seller_addr: '',
+      seller_mobile: '',
+      seller_bank: '',
+      seller_bankNo: '',
+      buyer_addr: '',
+      buyer_mobile: '',
+      buyer_bank: '',
+      buyer_bankNo: '',
+      SPBM: '',
+      ...rest
+    })
+  })
+  return target
+}