瀏覽代碼

Merge branch 'develop' of http://120.55.44.4:10080/xujunjie/BomWeb into develop

ZhaoJun 1 年之前
父節點
當前提交
d76b078d1a

+ 5 - 49
src/pages/Detail/CommitAuditModal.js

@@ -41,6 +41,7 @@ import AttachmentTable from '@/components/AttachmentTable';
 import { getToken } from '@/utils/utils';
 import LuckyExcel from 'luckyexcel';
 import DDComponents from '@/components/DDComponents';
+import uploadExcelByUrl from '@/utils/uploadExcelByUrl';
 
 const { TextArea } = Input;
 const { Option } = Select;
@@ -719,9 +720,8 @@ function CommitAuditModal(props) {
         next_template_id: version.template_id,
       };
       if (serviceNode.node_type_psr == 3 || serviceNode.node_type_psr == 4) {
-        let project = projectList.find(item => item.id == version?.project_id);
-        let projectName = project?.project_name || '';
-        let sheetData = await uploadExcelByUrl(serviceNode.node_type_psr, version.id, projectName);
+        let project = projectList.find(item => item.id == version?.project_id)|| {};
+        let sheetData = await uploadExcelByUrl(serviceNode.node_type_psr, version.id, project);
         params.data = JSON.stringify(sheetData);
       }
       // params.data = await uploadExcelByUrl(3, version.id);
@@ -908,55 +908,11 @@ function CommitAuditModal(props) {
   );
 }
 
-const uploadExcelByUrl = (nodeType, versionId) => {
-  const TEMPLATE_URL =
-    'https://water-service-test.oss-cn-hangzhou.aliyuncs.com/doc/contract/2023-06-29/ed0d5dcd-6ce0-40df-9d17-a1f69245dbb9.xlsx';
-  const TEMPLATE_URL2 =
-    'https://water-service-test.oss-cn-hangzhou.aliyuncs.com/doc/contract/2023-06-29/431733cd-0abc-4a68-a439-d24c466e9845.xlsx';
-
-  return new Promise((reslove, reject) => {
-    LuckyExcel.transformExcelToLuckyByUrl(
-      nodeType == 3 ? TEMPLATE_URL : TEMPLATE_URL2,
-      '模板.xlsx',
-      async (exportJson, luckysheetfile) => {
-        let [record] = await getExcel(versionId);
-
-        let len = exportJson.sheets.length;
-        const excelData = exportJson.sheets?.map(item => {
-          return { ...item, order: Number(item.order) };
-        });
-        delete record.id;
-        record.order = len;
-        record.index = String(len);
-        record.status = '0';
-        record.name = '投标成本';
-        var res = [...excelData, record];
-        // console.log(res);
-        reslove(JSON.stringify(res));
-      }
-    );
-  });
-};
-
-async function getExcel(gridKey) {
-  var formData = new FormData();
-  formData.append('gridKey', gridKey);
-  let res = await fetch(
-    `/api/v1/purchase/record/sheet?gridKey=${gridKey}&JWT-TOKEN=${getToken()}`,
-    {
-      method: 'POST',
-      body: formData,
-    }
-  ).then(response => response.text());
-  return JSON.parse(JSON.parse(res));
-}
-
-export default connect(({ xflow, detail, user, list }) => ({
+export default connect(({ xflow, detail, user }) => ({
   flowDetail: xflow.flowDetail,
   versionList: detail.versionList,
   currentUser: user.currentUser,
   userList: user.list,
   OSSData: detail.OSSData,
-  // 隐患:刷新页面后将会丢失projectList
-  projectList: list?.project?.list || [],
+  projectList: detail.project.list,
 }))(CommitAuditModal);

+ 1 - 1
src/pages/Detail/CurrentInfo.js

@@ -23,4 +23,4 @@ function CurrentInfo(props) {
   );
 }
 
-export default connect(({ list }) => ({ projectList: list?.project?.list || [] }))(CurrentInfo);
+export default connect(({ detail }) => ({ projectList: detail.project.list }))(CurrentInfo);

+ 3 - 0
src/pages/Detail/Index.js

@@ -498,6 +498,9 @@ function Detail(props) {
         project_id: projectId,
       },
     });
+    dispatch({
+      type: 'detail/queryProject',
+    });
 
     // dispatch({
     //   type: 'detail/queryListParentByUser',

+ 100 - 34
src/pages/Detail/LuckySheet.js

@@ -139,6 +139,9 @@ class LuckySheet extends React.Component {
             this.luckysheet.setCellFormat(0, 0, 'bg', '#fff');
           }, 100);
         },
+        workbookCreateAfter: options => {
+          this.luckysheet.refreshFormula();
+        },
       },
     };
     if (version) {
@@ -625,50 +628,28 @@ class LuckySheet extends React.Component {
     return comment;
   }
 
-  async goalSeek(type, goal,setting) {
-    let luckysheet = this.luckysheet;
+  async goalSeek(type, goal, setting) {
+    const luckysheet = this.luckysheet;
     const sheet = this.luckysheet.getSheet({
-      name: '毛利概算'
-    })
-    let order = sheet.order;
-
-    const fn = function(x) {
-      return new Promise(resolve => {
-        luckysheet.setCellValue(9, 2, x.toFixed(4), {
-          order,
-          success: () => {
-            luckysheet.refreshFormula(() => {
-              let row;
-              if (type == 1) {
-                row = 1;
-              } else if (type == 2) {
-                row = 4;
-              } else {
-                row = 5;
-              }
-              let data = luckysheet.getCellValue(row, 2, {
-                order,
-              });
-              console.log(data);
-              resolve(data);
-            });
-          },
-        });
-      });
-    };
+      name: '毛利概算',
+    });
+    const order = sheet.order;
+
     try {
       let defaultValue = luckysheet.getCellValue(9, 2, {
         order,
       });
       const result = await GoalSeek({
         goal,
-        fn,
-        fnParams: [defaultValue],
+        fn: fn,
+        fnParams: [defaultValue, sheet.data, type],
         maxIterations: 1000,
         independentVariableIdx: 0,
-        ...setting
+        ...setting,
+      });
+      luckysheet.setCellValue(9, 2, result, {
+        order,
       });
-      console.log(result);
     } catch (error) {
       console.log(error);
     }
@@ -686,5 +667,90 @@ class LuckySheet extends React.Component {
     );
   }
 }
+function fn(C10, data, type) {
+  let C50 = data[49][2].v || 0;
+  let C49 = data[48][2].v || 0;
+  let C20 = data[19][2].v || 0;
+  let C21 = data[20][2].v || 0;
+  let C9 = data[8][2].v || 0;
+  let C15 = data[14][2].v || 0;
+  let C16 = data[15][2].v || 0;
+  let C17 = data[16][2].v || 0;
+  let C18 = data[17][2].v || 0;
+  let C19 = data[18][2].v || 0;
+  let C51 = data[50][2].v || 0;
+
+  let G42 = data[41][6].v || 0;
+  let G12 = data[11][6].v || 0;
+  let G13 = data[12][6].v || 0;
+  let G7 = data[6][6].v || 0;
+  let G8 = data[7][6].v || 0;
+  switch (type) {
+    case 1:
+      // 净利率
+      return C3() / C59();
+    case 2:
+      // 贡献毛利率
+      return C4() / C59();
+    case 3:
+      // 合同总价
+      return C59();
+  }
+
+  function C59() {
+    return C58() + C57() + C56() + C54();
+  }
 
+  function C4() {
+    return C3() + C50 + C49 + C48() + G42 + C43();
+  }
+  function C48() {
+    return C59() * G12;
+  }
+  function C58() {
+    return (C56() + C57()) * 0.12;
+  }
+  function C57() {
+    return (((C20 + C21) * C10) / (1 + G7)) * G7 - ((C20 + C21) / (1 + G8)) * G8;
+  }
+  function C56() {
+    return (
+      (((C15 + C16 + C17 + C18 + C19) * C10) / (1 + G7)) * G7 -
+      ((C15 + C16 + C17 + C18 + C19) / (1 + G7)) * G7
+    );
+  }
+  function C54() {
+    return SUM(2, [14, 20]) * C10;
+  }
+
+  function C3() {
+    return C55() - C53();
+  }
+  function C55() {
+    return C59() / (1 + G7);
+  }
+  function C53() {
+    return C52() - ((SUM(2, [14, 18]) / (1 + G7)) * G7 + (SUM(2, [19, 20]) / (1 + G8)) * G8);
+  }
+  function C52() {
+    return SUM(2, [14, 50]);
+  }
+  function C43() {
+    return C59() * G13;
+  }
+  function C44() {
+    return C59() * C9;
+  }
+
+  function SUM(x, [y1, y2]) {
+    let total = 0;
+    for (let i = y1; i < y2; i++) {
+      const item = data[i][x];
+      if (!isNaN(item.v)) {
+        total += item.v;
+      }
+    }
+    return total;
+  }
+}
 export default LuckySheet;

+ 2 - 4
src/pages/Detail/PsrControl.js

@@ -31,13 +31,11 @@ function PsrControl(props) {
     try {
       if (type == 3) {
         setting = {
-          maxStep: 0.0001,
-          percentTolerance: 0.01,
+          customToleranceFn: res => Math.abs(res - value) < 1
         };
       } else {
         setting = {
-          maxStep: 0.03,
-          percentTolerance: 1,
+          customToleranceFn: res => Math.abs(res - value) < 0.00001
         };
       }
       sheetRef.current.goalSeek(type, value, setting);

+ 10 - 6
src/pages/Detail/models/detail.js

@@ -1,16 +1,13 @@
 import {
   queryFlowInfo,
-  // commitSheet,
   querySheet,
   queryHistory,
-  // queryHistoryDetail,
   queryComment,
   addComment,
   createExcel,
   queryExcel,
   updateFlowInfo,
   submitAudit,
-  // approve,
   queryFiles,
   deleteFiles,
 } from '@/services/PurchaseList';
@@ -39,6 +36,7 @@ import {
   queryBindClassify,
   ChartTempOSSData,
 } from '@/services/boom';
+import { queryApproval } from '@/services/approval';
 import { queryRole } from '@/services/SysAdmin';
 import { setCurrentUser } from '@/utils/authority';
 import { queryProjectMenu } from '@/services/SysAdmin';
@@ -82,15 +80,21 @@ export default {
     classifyList: [],
     OSSData: {},
     excelFileList: [],
+    project: {
+      list: [],
+      pagination: false,
+    },
   },
 
   effects: {
-    *queryAuthority({ payload, callback }, { call, put }) {
-      const response = yield call(queryAuthority, payload);
+    *queryProject({ callback }, { call, put }) {
+      const response = yield call(queryApproval, { pageSize: 99999 });
       if (response) {
         yield put({
           type: 'save',
-          payload: { authority: response.data },
+          payload: {
+            project: response.data,
+          },
         });
       }
     },

+ 4 - 1
src/pages/Temp/index.js

@@ -20,7 +20,10 @@ function Index(props) {
   const luckysheetRef = useRef();
 
   const onClick = async type => {
-    let data = await uploadExcelByUrl(type, versionId, 'test水厂');
+    let data = await uploadExcelByUrl(type, versionId, {
+      project_name: 'test水厂',
+      project_full_code: 'TESTCODE',
+    });
     luckysheetRef.current.destroy();
     luckysheetRef.current.create({
       data,

+ 65 - 35
src/utils/GoalSeek.js

@@ -2,16 +2,17 @@ const IsNanError = TypeError('resulted in NaN');
 const FailedToConvergeError = Error('failed to converge');
 const InvalidInputsError = Error('invalid inputs');
 
-export default async function GoalSeek({
-  fn,
-  fnParams,
-  percentTolerance,
-  customToleranceFn,
-  maxIterations,
-  maxStep,
-  goal,
-  independentVariableIdx,
-}) {
+export default async function GoalSeek(options) {
+  const {
+    fn,
+    fnParams,
+    percentTolerance,
+    customToleranceFn,
+    maxIterations,
+    maxStep,
+    goal,
+    independentVariableIdx,
+  } = options;
   if (typeof customToleranceFn !== 'function') {
     if (!percentTolerance) {
       throw InvalidInputsError;
@@ -19,49 +20,78 @@ export default async function GoalSeek({
   }
   let g;
   let y;
-  let y1;
-  let oldGuess;
+  let oldY = 0;
+  let oldGuess = fnParams[independentVariableIdx];
   let newGuess;
   let res;
+  let max;
+  let min;
   const absoluteTolerance = ((percentTolerance || 0) / 100) * goal;
+  
+  async function getMin(minGuest) {
+    let copyParams = [...fnParams]
+    copyParams[independentVariableIdx] = minGuest
+    res = await fn(...copyParams);
+    if (res > goal) {
+      minGuest -= oldGuess / 2;
+      await getMin(minGuest);
+    }
+    return minGuest;
+  }
+
+  async function getMax(maxGuest) {
+    let copyParams = [...fnParams]
+    copyParams[independentVariableIdx] = maxGuest
+    res = await fn(...copyParams);
+    if (res < goal) {
+      maxGuest += oldGuess / 2;
+      await getMax(maxGuest);
+    }
+    return maxGuest;
+  }
   // iterate through the guesses
   for (let i = 0; i < maxIterations; i++) {
     // define the root of the function as the error
     res = await fn(...fnParams);
     y = res - goal;
     if (isNaN(y)) throw IsNanError;
-    // was our initial guess a good one?
+    // 判断是否满足条件
     if (typeof customToleranceFn !== 'function') {
       if (Math.abs(y) <= Math.abs(absoluteTolerance)) return fnParams[independentVariableIdx];
     } else {
       if (customToleranceFn(res)) return fnParams[independentVariableIdx];
     }
-    // set the new guess, correcting for maxStep
+    // 获取要改变的变量
     oldGuess = fnParams[independentVariableIdx];
-    newGuess = oldGuess + y;
-    if (Math.abs(newGuess - oldGuess) > maxStep) {
-      if (newGuess > oldGuess) {
-        newGuess = oldGuess + maxStep;
-      } else {
-        newGuess = oldGuess - maxStep;
+
+    // newGuess = oldGuess + y;
+    // if (Math.abs(newGuess - oldGuess) > maxStep) {
+    //   if (newGuess > oldGuess) {
+    //     newGuess = oldGuess + maxStep;
+    //   } else {
+    //     newGuess = oldGuess - maxStep;
+    //   }
+    // }
+    // 获取最大值和最小值
+    if (y > 0) {
+      max = oldGuess;
+      if (!min) {
+        min = await getMin(oldGuess / 2);
       }
-    }
-    fnParams[independentVariableIdx] = newGuess;
-    // re-run the fn with the new guess
-    y1 = (await fn(...fnParams)) - goal;
-    if (isNaN(y1)) throw IsNanError;
-    // calculate the error
-    g = (y1 - y) / y;
-    if (g === 0) g = 0.0001;
-    // set the new guess based on the error, correcting for maxStep
-    newGuess = oldGuess - y / g;
-    if (maxStep && Math.abs(newGuess - oldGuess) > maxStep) {
-      if (newGuess > oldGuess) {
-        newGuess = oldGuess + maxStep;
-      } else {
-        newGuess = oldGuess - maxStep;
+    } else {
+      min = oldGuess;
+      if (!max) {
+        max = await getMax(oldGuess * 2);
       }
     }
+    // if(oldY) {
+    //   if(y > oldY) {
+
+    //   }
+    // }
+    // oldY = y;
+    // 利用二分法查询
+    newGuess = (min + max) / 2;
     fnParams[independentVariableIdx] = newGuess;
   }
   // done with iterations, and we failed to converge

+ 55 - 11
src/utils/uploadExcelByUrl.js

@@ -1,8 +1,12 @@
 import LuckyExcel from 'luckyexcel';
 import { getToken } from '@/utils/utils';
+import moment from 'moment';
+window.moment = moment
 
-const uploadExcelByUrl = (nodeType, versionId, projectName) => {
-  const TEMPLATE_URL = 'https://gt-digitization.oss-cn-hangzhou.aliyuncs.com/public/bom/psr0913.xlsx';
+
+const uploadExcelByUrl = (nodeType, versionId, project) => {
+  const TEMPLATE_URL =
+    'https://gt-digitization.oss-cn-hangzhou.aliyuncs.com/public/bom/psr0913.xlsx';
 
   return new Promise((resolve, reject) => {
     LuckyExcel.transformExcelToLuckyByUrl(
@@ -25,11 +29,11 @@ const uploadExcelByUrl = (nodeType, versionId, projectName) => {
 
         // 处理psr预算
         excelData[1].status = 1;
-        res.push(initPSR(excelData[1], category, projectName));
+        res.push(initPSR(excelData[1], category, project?.project_name));
 
         if (nodeType == 4) {
           // 处理现金流
-          res.push(initActual(excelData[3], category, projectName));
+          res.push(initActual(excelData[3], category, project));
         }
 
         res.push(record);
@@ -70,6 +74,7 @@ function getCellValue(cell) {
 }
 
 function formatNumber(str) {
+  if (str.length < 6) return str;
   const number = parseFloat(str);
   if (!isNaN(number) && number % 1 !== 0) {
     return number.toFixed(1);
@@ -123,16 +128,50 @@ function getCategoryData(bom) {
 }
 
 // 处理现金流表
-function initActual(actual, category, projectName) {
+function initActual(actual, category, project) {
   let actualCategory = [];
   actual.celldata.forEach((item, i, celldata) => {
     if (item.c == 0 && item.v?.v) {
-      // 处理序号转float出现多余小数的情况
-      item.v.v = formatNumber(item.v?.v);
-    }
-    if (item.c == 1 && item.r == 0) item.v.v = projectName;
-    // 清单分类的总价填入对应预算列
-    if (item.c == 2) {
+      // LuckyExcel会将序号转float后再次转回string,导致小数出现精度问题
+      // 序号后超过2位数的固定处理,不超过两位数的保留一位小数
+      switch (item.r) {
+        case 65:
+          item.v.v = '5.10';
+          break;
+        case 66:
+          item.v.v = '5.11';
+          break;
+        case 126:
+          item.v.v = '9.12';
+          break;
+        case 127:
+          item.v.v = '9.13';
+          break;
+        case 147:
+          item.v.v = '10.12';
+          break;
+        case 148:
+          item.v.v = '10.13';
+          break;
+        case 262:
+          item.v.v = '17.15';
+          break;
+        case 264:
+          item.v.v = '17.17';
+        case 266:
+          item.v.v = '17.19';
+          break;
+        default:
+          // 处理序号转float出现多余小数的情况
+          item.v.v = formatNumber(item.v?.v);
+          break;
+      }
+    } else if (item.c == 1 && item.r == 0) {
+      item.v.v = project?.project_name;
+    } else if (item.c == 1 && item.r == 1) {
+      item.v.v = project?.project_full_code;
+    } else if (item.c == 2) {
+      // 清单分类的总价填入对应预算列
       // c=2 为名称列
       let value = getCellValue(item.v);
       // 判断该行是否为类型总列
@@ -140,6 +179,11 @@ function initActual(actual, category, projectName) {
         // 名称后第三列为预算列
         celldata[i + 3].v.v = category[value];
       }
+    } else if (item.c == 12 && item.r == 5) {
+      // 设置第一个月时间
+      let start_date = moment("1900-01-01 00:00:00");
+      let end_date = moment().startOf('month')
+      item.v.v = end_date.diff(start_date, 'days');
     }
   });
   return actual;