unit_price_model.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. /**
  2. * 单价业务模型
  3. *
  4. * @author CaiAoLin
  5. * @date 2017/6/30
  6. * @version
  7. */
  8. import mongoose from "mongoose";
  9. import BaseModel from "../../common/base/base_model"
  10. import GLJTypeConst from "../../common/const/glj_type_const"
  11. import CounterModel from "./counter_model"
  12. import MixRatioModel from "./mix_ratio_model";
  13. import _ from "lodash";
  14. const scMathUtil = require('../../../public/scMathUtil').getUtil();
  15. let collectionName = 'unit_price';
  16. let decimal_facade = require('../../main/facade/decimal_facade');
  17. let gljListModel = mongoose.model("glj_list");
  18. let original_calc_model = mongoose.model('original_calc');
  19. let freight_calc_model = mongoose.model('freight_calc');
  20. let gljUtil = require('../../../public/gljUtil');
  21. const uuidV1 = require('uuid/v1');
  22. class UnitPriceModel extends BaseModel {
  23. /**
  24. * 构造函数
  25. *
  26. * @return {void}
  27. */
  28. constructor() {
  29. let parent = super();
  30. parent.model = mongoose.model(collectionName);
  31. parent.init();
  32. }
  33. /**
  34. * 根据单价文件id获取单价数据
  35. *
  36. * @param {Number} fileId
  37. * @return {Promise}
  38. */
  39. async getDataByFileId(fileId) {
  40. fileId = parseInt(fileId);
  41. if (isNaN(fileId) || fileId <= 0) {
  42. return null;
  43. }
  44. let unitPriceList = await this.db.model.find({unit_price_file_id: fileId});
  45. if (unitPriceList.length <= 0) {
  46. return null;
  47. }
  48. // 整理数据
  49. let result = {};
  50. for(let tmp of unitPriceList) {
  51. let index = this.getIndex(tmp,['code','name','specs','unit','type'])
  52. result[index] = tmp;
  53. }
  54. return result;
  55. }
  56. /**
  57. * 设置场景
  58. *
  59. * @param {string} scene
  60. * @return {void}
  61. */
  62. setScene(scene = '') {
  63. switch (scene) {
  64. // 新增数据的验证规则
  65. case 'add':
  66. this.model.schema.path('base_price').required(true);
  67. this.model.schema.path('market_price').required(true);
  68. this.model.schema.path('name').required(true);
  69. this.model.schema.path('code').required(true);
  70. // this.model.schema.path('unit').required(true);
  71. this.model.schema.path('type').required(true);
  72. this.model.schema.path('unit_price_file_id').required(true);
  73. }
  74. }
  75. /**
  76. * 新增单价数据
  77. *
  78. * @param {Object} data
  79. * @param {Number} unitPriceFileId
  80. * @param {Number} gljCount
  81. * @return {Promise} 返回数据以及是否新增
  82. */
  83. async addUnitPrice(data, unitPriceFileId,operation='add', gljCount = 0) {
  84. if (data.original_code===undefined||data.code === undefined || data.project_id === undefined || data.name === undefined
  85. || data.market_price === undefined) {
  86. return [null, false];
  87. }
  88. // 先查找是否有原始code相同的记录
  89. let unitPriceData = await this.db.model.find({original_code: data.original_code, unit_price_file_id: unitPriceFileId}).sort('code').exec();
  90. // 如果有记录,判断是否存在一样的名称,单位...等,有则直接返回数据
  91. let unitPrice=null;
  92. if(operation=='add'){//新增操作时,要把code也一起判断,是否完全一样。(新增的时候有可能存在编码一样,但是名称规格等不一样的情况,这种情况的话编码不用改变)
  93. unitPrice = this.isPropertyInclude(unitPriceData,['code','name','specs','unit','type'],data);
  94. }else {//修改操作时,code不用加入判断,因为code是需要改变的
  95. unitPrice = this.isPropertyInclude(unitPriceData,['name','specs','unit','type'],data);
  96. }
  97. if(unitPrice){
  98. return [unitPrice, false];
  99. }
  100. // 如果不存在基价单价,则在数据源中获取
  101. if (data.base_price === undefined) {
  102. let firstUnitPrice = unitPriceData[0] !== undefined ? unitPriceData[0] : [];
  103. data.base_price = firstUnitPrice.base_price !== undefined ? firstUnitPrice.base_price : 0;
  104. data.type = firstUnitPrice.type !== undefined ? firstUnitPrice.type : 0;
  105. }
  106. let insertData = {
  107. code: data.code,
  108. base_price: data.base_price,
  109. market_price: data.market_price,
  110. unit_price_file_id: unitPriceFileId,
  111. name: data.name,
  112. specs:data.specs?data.specs:'',
  113. original_code:data.original_code,
  114. unit:data.unit?data.unit:'',
  115. type: data.type,
  116. short_name: data.shortName !== undefined ? data.shortName : '',
  117. glj_id: data.glj_id,
  118. is_add:0,
  119. grossWeightCoe:data.grossWeightCoe,
  120. purchaseStorageRate:data.purchaseStorageRate,
  121. offSiteTransportLossRate:data.offSiteTransportLossRate,
  122. handlingLossRate:data.handlingLossRate
  123. };
  124. if(data.from=='cpt') insertData.is_add=1;//如果是来自补充工料机,则都添加新增标记
  125. if(operation=='add' && insertData.code != insertData.original_code) insertData.is_add=1;//添加的时候如果是复制整块来的,可能在源项目中是新增的工料机,这里也要添上
  126. if (unitPriceData&&unitPriceData.length>0&&operation!='add') {// 如果原始编码能找到,但不存在一样的编号,名称,单位.型号等,更改code和添加新增标记,新增的时候除外。新增的情况下能到这一步说明有存在编码一致但其它属性不一致的情况,所以不用更改编码
  127. //insertData.code = data.original_code+"-"+unitPriceData.length;
  128. insertData.code = data.original_code+"-"+this.getLastNumber(data.original_code,unitPriceData);
  129. insertData.is_add=1;
  130. }
  131. let addPriceResult = await this.add(insertData);
  132. return [addPriceResult, true];
  133. }
  134. getLastNumber(original_code,unitPriceData){
  135. let codeArray = _.map(unitPriceData,'code');
  136. let last = 1;
  137. while (true){
  138. if(_.includes(codeArray,original_code+"-"+last)){
  139. last +=1
  140. }else {
  141. break;
  142. }
  143. }
  144. return last;
  145. }
  146. /**
  147. * 新增记录
  148. *
  149. * @param {object} data
  150. * @return {Promise}
  151. */
  152. async add(data) {
  153. let counterModel = new CounterModel();
  154. if (data instanceof Array) {
  155. // 如果是批量新增
  156. await this.setIDfromCounter(collectionName,data);
  157. /* for(let tmp in data) {
  158. data[tmp].id = await counterModel.getId(collectionName);
  159. } */
  160. } else {
  161. data.id = await counterModel.getId(collectionName);
  162. }
  163. this.setScene('add');
  164. return await this.db.model.create(data);
  165. }
  166. /**
  167. * 判断数据中是否包含某个市场价格的记录
  168. *
  169. * @param {Array} data
  170. * @param {Number} price
  171. * @return {Number}
  172. */
  173. isPriceIncluded(data, price) {
  174. let index = -1;
  175. if (data.length <= 0) {
  176. return index;
  177. }
  178. for(let tmp in data) {
  179. if (data[tmp].market_price === price) {
  180. index = tmp;
  181. break;
  182. }
  183. }
  184. return index;
  185. }
  186. isPropertyInclude(data,pops,obj){
  187. let condition={},me = this;
  188. if (data.length <= 0) {
  189. return null;
  190. }
  191. if(pops instanceof Array){
  192. return _.find(data,function (d) {
  193. return me.getIndex(d,pops) == me.getIndex(obj,pops)
  194. });
  195. }else {
  196. condition[pops]=obj[pops];
  197. return _.find(data,condition);
  198. }
  199. }
  200. /**
  201. * 更新市场单价
  202. *
  203. * @param {Object} condition
  204. * @param {Object} updateData
  205. * @param {String} extend
  206. * @return {Promise}
  207. */
  208. async updatePrice(condition, updateData, extend = '') {
  209. if (Object.keys(condition).length <= 0 || Object.keys(updateData).length <= 0) {
  210. return false;
  211. }
  212. // 首先查找相应的数据判断工料机类型
  213. let unitPriceData = await this.findDataByCondition(condition);
  214. if (!unitPriceData) {
  215. throw '找不到对应的单价数据';
  216. }
  217. /* // 基价单价的计算-----先不考虑同步
  218. switch (unitPriceData.type) {
  219. // 主材、设备自动赋值基价单价=市场单价
  220. case GLJTypeConst.MAIN_MATERIAL:
  221. case GLJTypeConst.EQUIPMENT:
  222. updateData.base_price = updateData.market_price;
  223. break;
  224. }*/
  225. // 额外更新数据
  226. if (extend !== '') {
  227. extend = JSON.parse(extend);
  228. let indexList = ['code','name','specs','unit','type'];
  229. for (let conKey in extend) {
  230. let extendUpdateData = {
  231. market_price: extend[conKey].market_price,
  232. };
  233. let tmpCondition = {
  234. unit_price_file_id: unitPriceData.unit_price_file_id,
  235. };
  236. let keyList = conKey.split("|-|");
  237. for(let i = 1;i<keyList.length;i++){
  238. if(keyList[i]!='null'){
  239. tmpCondition[indexList[i]]=keyList[i];
  240. }
  241. }
  242. let extendResult = await this.db.update(tmpCondition, extendUpdateData);
  243. if (!extendResult) {
  244. throw '更新额外数据,编码为' + code + '的数据失败!';
  245. }
  246. }
  247. }
  248. let result = await this.db.update(condition, updateData);
  249. return result.ok !== undefined && result.ok === 1;
  250. }
  251. async updateUnitPrice(data){
  252. //查找并更新单价
  253. let doc={},newValueMap={};
  254. doc[data.field]=data.newval;
  255. newValueMap[data.id]=doc;
  256. let unitPrice = await this.db.findAndModify({id:data.id,unit_price_file_id:data.unit_price_file_id},doc);
  257. if(!unitPrice){
  258. throw "没有找到对应的单价";
  259. }
  260. let rList=await this.checkAndUpdateParent(unitPrice,data.field,data.project_id,newValueMap);
  261. if(data.ext){
  262. let elecPrice = await this.db.findAndModify({id:data.ext.id,unit_price_file_id:data.ext.unit_price_file_id},data.ext.doc);
  263. let nm = {};
  264. nm[data.ext.id] = data.ext.doc;
  265. let erList=await this.checkAndUpdateParent(elecPrice,"market_price",data.project_id,nm);
  266. rList = rList.concat(erList);
  267. }
  268. return rList;
  269. }
  270. async updateCalcMaterial(datas){
  271. for(let data of datas){
  272. let doc = data.ext?data.ext:{};
  273. doc[data.updateField] = data.value;
  274. let unitPrice = await this.db.findAndModify({id:data.id,unit_price_file_id:data.unit_price_file_id},doc);
  275. if(data.updateField == 'calcMaterial' && doc['calcMaterial'] == 0){//标记为0即删除材料计算标记,要删除其下挂的原价计算,运费计算,定额计算
  276. let connect_key = gljUtil.getIndex(unitPrice);
  277. await original_calc_model.deleteMany({unit_price_file_id:data.unit_price_file_id,connect_key:connect_key});
  278. await freight_calc_model.deleteMany({unit_price_file_id:data.unit_price_file_id,connect_key:connect_key});
  279. //to do 删除定额计算
  280. }
  281. if(!unitPrice){
  282. throw "没有找到对应的单价";
  283. }
  284. }
  285. return datas;
  286. }
  287. needUpdateParent(connect_key){
  288. let noNeedUpdateType = ["202","203","204"];//父类型为混凝土、砂浆,配合比,类型的,不用更新价格
  289. let keyList = connect_key.split("|-|");
  290. return noNeedUpdateType.indexOf(keyList[4]) == -1
  291. }
  292. async checkAndUpdateParent(unitPrice,field,project_id,newValueMap,batchUpdate=false){//检查是否属于某个工料机的组成物,如果是,并且不是批量更新的情况下,直接更新,如果是批量更新,返回更新任务
  293. //查找是否是属于某个项目工料机的组成物
  294. let mixRatioModel = new MixRatioModel();
  295. let condition = {unit_price_file_id:unitPrice.unit_price_file_id, code:unitPrice.code,name: unitPrice.name, specs: unitPrice.specs,unit:unitPrice.unit,type:unitPrice.type};
  296. let mixRatioList = await mixRatioModel.findDataByCondition(condition, null, false);
  297. let connectKeyMap={};
  298. //找到则计算项目工料机组成物的价格并更新
  299. let rList= [];
  300. if(mixRatioList&&mixRatioList.length>0){
  301. for(let m of mixRatioList){
  302. // 父类型不为混凝土、砂浆,配合比,类型的,才要更新价格
  303. if(this.needUpdateParent(m.connect_key) && !connectKeyMap.hasOwnProperty(m.connect_key)){//为了去重复,组成物会与其它项目同步,所以有可能重复。
  304. rList.push(await this.updateParentUnitPrice(m,field,project_id,newValueMap,batchUpdate));
  305. connectKeyMap[m.connect_key]=true;
  306. }
  307. }
  308. }
  309. return rList;
  310. }
  311. async batchUpdatePrices(data){//批量更新
  312. let tasks = [];
  313. let parentTask = [];
  314. let newValueMap = {};
  315. let needCheckDatas= [];
  316. for(let d of data){//第一次循环生成更新提交的记录,并生成一个新值的映射表,为更新父节点使用
  317. let condition = {id:d.unit_price.id,unit_price_file_id:d.unit_price.unit_price_file_id};
  318. let doc = d.ext?d.ext:{};
  319. if(d.field){//共用接口后有可能只更新其它属性,不更新价格
  320. doc[d.field]=d.newval;
  321. newValueMap[d.unit_price.id] = doc;
  322. needCheckDatas.push(d);
  323. }
  324. tasks.push(this.generateUpdateTask(condition,doc));
  325. }
  326. for(let d of needCheckDatas){//第二次更新父节点
  327. let rList = await this.checkAndUpdateParent(d.unit_price,d.field,d.project_id,newValueMap,true);
  328. parentTask = parentTask.concat(rList);
  329. }
  330. tasks = tasks.concat(parentTask);
  331. tasks.length>0?this.model.bulkWrite(tasks):'';
  332. return parentTask;
  333. }
  334. async updateParentUnitPrice(mixRatio,fieid,project_id,newValueMap,batchUpdate){//batchUpdate 批量更新标记,如果true,只生成task
  335. let decimalObject =project_id?await decimal_facade.getProjectDecimal(project_id):null;
  336. let quantity_decimal = (decimalObject&&decimalObject.glj&&decimalObject.glj.quantity)?decimalObject.glj.quantity:3;
  337. let price_decimal = (decimalObject&&decimalObject.glj&&decimalObject.glj.unitPrice)?decimalObject.glj.unitPrice:2;
  338. //查找该工料机所有组成物
  339. let indexList = ['code','name','specs','unit','type'];
  340. let mixRatioModel = new MixRatioModel();
  341. let mixRatioMap = await mixRatioModel.findDataByCondition({unit_price_file_id:mixRatio.unit_price_file_id,connect_key:mixRatio.connect_key}, null, false,indexList);
  342. //查找对应的价格
  343. let codeList = [];
  344. let nameList = [];
  345. let specsList= [];
  346. let typeList = [];
  347. let unitList = [];
  348. for(let mk in mixRatioMap){
  349. codeList.push(mixRatioMap[mk].code);
  350. nameList.push(mixRatioMap[mk].name);
  351. specsList.push(mixRatioMap[mk].specs);
  352. typeList.push(mixRatioMap[mk].type);
  353. unitList.push(mixRatioMap[mk].unit);
  354. }
  355. let condition = {unit_price_file_id: mixRatio.unit_price_file_id,code: {"$in": codeList}, name: {"$in": nameList},specs:{"$in": specsList},type:{"$in": typeList},unit:{"$in": unitList}};
  356. let priceMap = await this.findDataByCondition(condition, {_id: 0}, false, indexList);
  357. let sumPrice=0;
  358. for(let pk in priceMap){
  359. let price = scMathUtil.roundForObj(priceMap[pk][fieid],price_decimal);
  360. let consumption = scMathUtil.roundForObj(mixRatioMap[pk].consumption,quantity_decimal);
  361. if(newValueMap[priceMap[pk].id]){//是需要更新的记录,取当前新的值
  362. price = scMathUtil.roundForObj(newValueMap[priceMap[pk].id][fieid],price_decimal);
  363. }
  364. sumPrice +=scMathUtil.roundForObj(price*consumption,price_decimal);
  365. }
  366. sumPrice= scMathUtil.roundForObj(sumPrice,price_decimal);
  367. if(sumPrice<=0){
  368. return null;
  369. }
  370. //更新父价格
  371. let keyList = mixRatio.connect_key.split("|-|");
  372. let pcondition = {
  373. unit_price_file_id:mixRatio.unit_price_file_id,
  374. code:keyList[0]
  375. };
  376. for(let i = 1;i<keyList.length;i++){
  377. if(keyList[i]!='null'){
  378. pcondition[indexList[i]]=keyList[i];
  379. }
  380. }
  381. let doc={};
  382. doc[fieid]=sumPrice;
  383. if(batchUpdate == true){
  384. return this.generateUpdateTask(pcondition,doc);
  385. }else {
  386. let uprice = await this.db.findAndModify(pcondition,doc,{new: true});
  387. //uprice[fieid]=sumPrice;
  388. return uprice;
  389. }
  390. }
  391. generateUpdateTask(condition,doc) {
  392. let task = {
  393. updateOne:{
  394. filter:condition,
  395. update:doc
  396. }
  397. };
  398. return task
  399. }
  400. /**
  401. * 复制单价文件数据
  402. *
  403. * @param {Number} currentUnitPriceId
  404. * @param {Number} changeUnitPriceId
  405. * @return {Promise}
  406. */
  407. async copyNotExist(currentUnitPriceId, changeUnitPriceId,projectId) {
  408. let result = false;
  409. // 首先查找原单价文件id下的数据
  410. let currentUnitList = await this.model.find({unit_price_file_id: currentUnitPriceId}).lean();
  411. if (currentUnitList === null) {
  412. return result;
  413. }
  414. let gljList = await gljListModel.find({'project_id':projectId}).lean();
  415. let gljMap = {};//用来记录glj的映射表,本项目有使用的工料机才需要copy过去
  416. for(let g of gljList){
  417. let g_index = this.getIndex(g,['code','name','specs','unit','type']);
  418. gljMap[g_index] = g;
  419. }
  420. let codeList = [];
  421. let nameList =[];
  422. for (let tmp of currentUnitList) {
  423. if (codeList.indexOf(tmp.code) >= 0) {
  424. continue;
  425. }
  426. codeList.push(tmp.code);
  427. if(nameList.indexOf(tmp.name)>=0){
  428. continue
  429. }
  430. nameList.push(tmp.name);
  431. }
  432. // 查找即将更替的单价文件是否存在对应的工料机数据 -- (这里只根据code和名称初步过滤,因为其它的几项更改的概率不大,在下一步的比较中再精确匹配)
  433. let condition = {unit_price_file_id: changeUnitPriceId, code: {"$in": codeList},name:{"$in": nameList}};
  434. let targetUnitList = await this.findDataByCondition(condition, null, false, ['code','name','specs','unit','type']);
  435. // 如果没有重叠的数据则原有的数据都复制一份
  436. let insertData = [];
  437. for (let tmp of currentUnitList) {
  438. let t_index = this.getIndex(tmp,['code','name','specs','unit','type']);
  439. if (targetUnitList !== null && targetUnitList[t_index] !== undefined) {
  440. continue;
  441. }
  442. if(gljMap[t_index]){//如果本项目有用到才复制
  443. delete tmp._id; // 删除原有id信息
  444. delete tmp.id;
  445. tmp.unit_price_file_id = changeUnitPriceId;
  446. insertData.push(tmp);
  447. }
  448. }
  449. let uResult = insertData.length > 0 ? await this.add(insertData) : true;
  450. let mixRatioModel = new MixRatioModel();
  451. let mResult = await mixRatioModel.copyNotExist(currentUnitPriceId, changeUnitPriceId,gljMap);//复制组成物
  452. let cResult = await this.copyMaterialNotExist(currentUnitPriceId, changeUnitPriceId,gljMap);
  453. return uResult&&mResult&&cResult;
  454. }
  455. async copyMaterialNotExist(currentUnitPriceId, changeUnitPriceId,gljMap,newFile = false){
  456. await this.copyCalcNotExist(currentUnitPriceId, changeUnitPriceId,gljMap,original_calc_model,newFile);//复制原价计算
  457. await this.copyCalcNotExist(currentUnitPriceId, changeUnitPriceId,gljMap,freight_calc_model,newFile);//复制运费计算
  458. return true;
  459. }
  460. async copyCalcNotExist(currentUnitPriceId, changeUnitPriceId,gljMap,model,newFile){
  461. let currentMap = {},targetMap = {}, insertData = [];
  462. //取原单价文件所有的原价、运费计算计录
  463. let currentList = await model.find({'unit_price_file_id':currentUnitPriceId}).lean();
  464. currentMap = _.groupBy(currentList,"connect_key");
  465. //切换后的单价文件所有的的组成物
  466. let targetList = await model.find({'unit_price_file_id':changeUnitPriceId});
  467. targetMap = _.groupBy(targetList,"connect_key");
  468. for(let ckey in currentMap){
  469. if(targetMap[ckey]){//如果切换后已经存在,则不用复制
  470. continue;
  471. }
  472. if(gljMap[ckey] || newFile == true){//在本项目中有用到 或者 新建的文件
  473. for(let c of currentMap[ckey]){
  474. delete c._id; // 删除原有id信息
  475. c.ID = uuidV1();
  476. c.unit_price_file_id = changeUnitPriceId;
  477. //更改下挂的定额工料机与项目工料机的关联关系
  478. if(c.ration_gljs){
  479. for(let rg of c.ration_gljs){
  480. rg.ID = uuidV1();
  481. }
  482. }
  483. if(c.rations){
  484. for(let r of c.rations){
  485. r.ID = uuidV1();
  486. }
  487. }
  488. insertData.push(c);
  489. }
  490. }
  491. }
  492. if(insertData.length > 0) await model.create(insertData);
  493. }
  494. }
  495. export default UnitPriceModel;