lrf402788946 4 年之前
父節點
當前提交
dcd3ea3478

+ 25 - 0
app/controller/bill/.bill.js

@@ -0,0 +1,25 @@
+module.exports = {
+  show: {
+    parameters: {
+      params: ["!id"],
+    },
+    service: "fetch",
+  },
+  index: {
+    parameters: {
+      query: {
+        client: "client",
+        owner:"owner",
+        "create_time@start": "create_time@start",
+        "create_time@end": "create_time@end",
+      },
+    },
+    service: "query",
+    options: {
+      query: ["skip", "limit"],
+      sort: ["meta.createdAt"],
+      desc: true,
+      count: true,
+    },
+  },
+};

+ 16 - 0
app/controller/bill/bill.js

@@ -0,0 +1,16 @@
+'use strict';
+
+// const _ = require('lodash');
+const meta = require('./.bill.js');
+const Controller = require('egg').Controller;
+const { CrudController } = require('naf-framework-mongoose/lib/controller');
+
+// 车辆表
+class BillController extends Controller {
+  constructor(ctx) {
+    super(ctx);
+    this.service = this.ctx.service.bill.bill;
+  }
+}
+
+module.exports = CrudController(BillController, meta);

+ 30 - 3
app/controller/order/.order.js

@@ -65,9 +65,9 @@ module.exports = {
         owner: "owner",
         principal: "principal",
         client: "client",
-        treaty:"treaty",
-        item:"item",
-        route:"route",
+        treaty: "treaty",
+        item: "item",
+        route: "route",
         "rq_send_time@start": "rq_send_time@start",
         "rq_send_time@end": "rq_send_time@end",
         "rq_arrive_time@start": "rq_arrive_time@start",
@@ -83,4 +83,31 @@ module.exports = {
       count: true,
     },
   },
+  clientCalculate: {
+    parameters: {
+      query: {
+        order_no: "%order_no%",
+        owner: "owner",
+        principal: "principal",
+        client: "client",
+        treaty: "treaty",
+        item: "item",
+        route: "route",
+        "rq_send_time@start": "rq_send_time@start",
+        "rq_send_time@end": "rq_send_time@end",
+        "rq_arrive_time@start": "rq_arrive_time@start",
+        "date@start": "create_time@start",
+        "date@end": "create_time@end",
+        is_js: "is_js",
+        status: "status",
+      },
+    },
+    service: "clientCalculate",
+    options: {
+      query: ["skip", "limit"],
+      sort: ["meta.createdAt"],
+      desc: true,
+      count: true,
+    },
+  },
 };

+ 16 - 1
app/controller/order/order.js

@@ -30,7 +30,6 @@ class OrderController extends Controller {
     this.ctx.ok();
   }
 
-
   /**
    * 修改支出
    */
@@ -46,6 +45,22 @@ class OrderController extends Controller {
     await this.service.principalChange(this.ctx.request.body);
     this.ctx.ok();
   }
+
+  /**
+   * 导出客户结算单
+   */
+  async clientExport() {
+    const res = await this.service.clientExport(this.ctx.request.body);
+    this.ctx.ok({ data: res });
+  }
+
+  /**
+   * 订单结算
+   */
+  async js() {
+    const res = await this.service.js(this.ctx.request.body);
+    this.ctx.ok({ data: res });
+  }
 }
 
 module.exports = CrudController(OrderController, meta);

+ 29 - 0
app/model/bill.js

@@ -0,0 +1,29 @@
+'use strict';
+const Schema = require('mongoose').Schema;
+const moment = require('moment');
+const metaPlugin = require('naf-framework-mongoose/lib/model/meta-plugin');
+// 结算单
+const Bill = {
+  client: {
+    type: String,
+    required: true,
+    maxLength: 200,
+    field: { label: '客户/供应商id', filter: true, required: true },
+  },
+  ids: { type: Array },
+  owner: { type: String, required: true }, // 创建人
+  create_time: {
+    type: String,
+    default: moment().format('YYYY-MM-DD HH:mm:ss'),
+  },
+};
+
+const schema = new Schema(Bill, { toJSON: { virtuals: true } });
+schema.index({ id: 1 });
+
+schema.plugin(metaPlugin);
+
+module.exports = app => {
+  const { mongoose } = app;
+  return mongoose.model('Bill', schema, 'bill');
+};

+ 2 - 0
app/model/client.js

@@ -1,5 +1,6 @@
 'use strict';
 const Schema = require('mongoose').Schema;
+const moment = require('moment');
 const metaPlugin = require('naf-framework-mongoose/lib/model/meta-plugin');
 // 客户
 const Client = {
@@ -61,6 +62,7 @@ const Client = {
       ],
     },
   }, // 状态:0=>使用;1禁用
+  create_time: { type: String, default: moment().format('YYYY-MM-DD HH:mm:ss') },
 };
 
 const schema = new Schema(Client, { toJSON: { virtuals: true } });

+ 3 - 1
app/model/daily.js

@@ -1,6 +1,6 @@
 'use strict';
-const moment = require('moment');
 const Schema = require('mongoose').Schema;
+const moment = require('moment');
 const metaPlugin = require('naf-framework-mongoose/lib/model/meta-plugin');
 // 车辆日常维护
 const daily = {
@@ -59,6 +59,8 @@ const daily = {
     },
     row: 6,
   },
+  create_time: { type: String, default: moment().format('YYYY-MM-DD HH:mm:ss') },
+
 };
 
 const schema = new Schema(daily, { toJSON: { virtuals: true } });

+ 2 - 0
app/model/driver.js

@@ -1,5 +1,6 @@
 'use strict';
 const Schema = require('mongoose').Schema;
+const moment = require('moment');
 const metaPlugin = require('naf-framework-mongoose/lib/model/meta-plugin');
 // 司机表
 const Driverchema = {
@@ -13,6 +14,7 @@ const Driverchema = {
   cc_time: { type: String, required: false, maxLength: 200, field: { required: true, label: '驾驶证年检日期', notable: true, type: 'date' } }, // 驾驶证年检日期
   qc_time: { type: String, required: false, maxLength: 200, field: { required: true, label: '资格证年检日期', notable: true, type: 'date' } }, // 资格证年检日期
   status: { type: String, required: false, maxLength: 200, default: '在籍', field: { label: '状态', type: 'radio', filter: 'select', list: [{ label: '在籍', value: '在籍' }, { label: '注销', value: '注销' }] } },
+  create_time: { type: String, default: moment().format('YYYY-MM-DD HH:mm:ss') },
 };
 
 

+ 3 - 0
app/model/item.js

@@ -1,5 +1,6 @@
 'use strict';
 const Schema = require('mongoose').Schema;
+const moment = require('moment');
 const metaPlugin = require('naf-framework-mongoose/lib/model/meta-plugin');
 // 项目
 const item = {
@@ -37,6 +38,8 @@ const item = {
     },
   }, // 状态:0=>使用;1禁用
   owner: { type: String, required: true }, // 创建人
+  create_time: { type: String, default: moment().format('YYYY-MM-DD HH:mm:ss') },
+
 };
 
 const schema = new Schema(item, { toJSON: { virtuals: true } });

+ 3 - 0
app/model/mode.js

@@ -1,4 +1,5 @@
 'use strict';
+const moment = require('moment');
 const Schema = require('mongoose').Schema;
 const metaPlugin = require('naf-framework-mongoose/lib/model/meta-plugin');
 // 方式
@@ -75,6 +76,8 @@ const mode = {
     },
   }, // 状态:0=>使用;1禁用
   owner: { type: String, required: true }, // 创建人
+  create_time: { type: String, default: moment().format('YYYY-MM-DD HH:mm:ss') },
+
 };
 
 const schema = new Schema(mode, { toJSON: { virtuals: true } });

+ 3 - 0
app/model/notice.js

@@ -1,5 +1,6 @@
 'use strict';
 const Schema = require('mongoose').Schema;
+const moment = require('moment');
 const metaPlugin = require('naf-framework-mongoose/lib/model/meta-plugin');
 // 消息
 const notice = {
@@ -48,6 +49,8 @@ const notice = {
       ],
     },
   }, // 状态:0=>使用;1禁用
+  create_time: { type: String, default: moment().format('YYYY-MM-DD HH:mm:ss') },
+
 };
 
 const schema = new Schema(notice, { toJSON: { virtuals: true } });

+ 3 - 1
app/model/order.js

@@ -54,7 +54,7 @@ const splitGoods = new Schema({
   remark: { type: String, maxLength: 200 }, // remark属于拆分过后的,只有原货物进行复制,拆分后的不需要复制,可以自己编辑
   pid: { type: String, maxLength: 200, required: true }, // 来源id;type为0时,从goods中去找;1时,就都是这个文档的事了
   type: { type: String, maxLength: 200, default: '0' }, // 0=>货物列表原始数据;1=>拆分数据;
-  status: { type: String, maxLength: 200, default: '0' }, // -1=>已到达; 0=>未装车; 1=>运输中; 2=>转运地签收; 3=>转运中
+  status: { type: String, maxLength: 200, default: '0' }, // -1=>已到达; 0=>未装车; 1=>运输中;
 });
 
 // 订单记录
@@ -232,6 +232,8 @@ const order = {
     type: [ recordList ],
     field: { label: '订单记录' },
   },
+  create_time: { type: String, default: moment().format('YYYY-MM-DD HH:mm:ss') },
+
 };
 
 const schema = new Schema(order, { toJSON: { virtuals: true } });

+ 3 - 0
app/model/route.js

@@ -1,5 +1,6 @@
 'use strict';
 const Schema = require('mongoose').Schema;
+const moment = require('moment');
 const metaPlugin = require('naf-framework-mongoose/lib/model/meta-plugin');
 // 线路
 const route = {
@@ -61,6 +62,8 @@ const route = {
     },
   }, // 状态:0=>使用;1禁用
   owner: { type: String, required: true }, // 创建人
+  create_time: { type: String, default: moment().format('YYYY-MM-DD HH:mm:ss') },
+
 };
 
 const schema = new Schema(route, { toJSON: { virtuals: true } });

+ 3 - 0
app/model/schedule.js

@@ -1,5 +1,6 @@
 'use strict';
 const Schema = require('mongoose').Schema;
+const moment = require('moment');
 const metaPlugin = require('naf-framework-mongoose/lib/model/meta-plugin');
 // 定时任务表
 const schedule = {
@@ -45,6 +46,8 @@ const schedule = {
       ],
     },
   }, // 状态:0=>使用;1禁用
+  create_time: { type: String, default: moment().format('YYYY-MM-DD HH:mm:ss') },
+
 };
 
 const schema = new Schema(schedule, { toJSON: { virtuals: true } });

+ 1 - 0
app/model/transport.js

@@ -132,6 +132,7 @@ const transport = {
       ],
     },
   },
+  create_time: { type: String, default: moment().format('YYYY-MM-DD HH:mm:ss') },
 };
 
 const schema = new Schema(transport, { toJSON: { virtuals: true } });

+ 1 - 0
app/router.js

@@ -18,5 +18,6 @@ module.exports = app => {
   require('./router/car')(app); // 车辆相关
   require('./router/order')(app); // 订单相关
   require('./router/transport')(app); // 运输相关
+  require('./router/bill')(app); // 结算单相关
 
 };

+ 10 - 0
app/router/bill.js

@@ -0,0 +1,10 @@
+'use strict';
+/**
+ * @param {Egg.Application} app - egg application
+ */
+module.exports = app => {
+  const prefix = '/api/servicezhwl';
+  const index = 'bill';
+  const { router, controller } = app;
+  router.resources('bill', `${prefix}/bill`, controller[index].bill); // index、create、show、destroy
+};

+ 6 - 0
app/router/order.js

@@ -6,6 +6,12 @@ module.exports = app => {
   const prefix = '/api/servicezhwl';
   const index = 'order';
   const { router, controller } = app;
+  // 订单结算
+  router.post('order', `${prefix}/order/js`, controller[index].order.js);
+  // 客户结算单导出
+  router.post('order', `${prefix}/order/client/calculate/export`, controller[index].order.clientExport);
+  // 客户结算单查询
+  router.get('order', `${prefix}/order/client/calculate`, controller[index].order.clientCalculate);
   // 订单负责人 principalChange
   router.post('order', `${prefix}/order/principal`, controller[index].order.principalChange);
   // 收入

+ 11 - 0
app/service/bill/bill.js

@@ -0,0 +1,11 @@
+'use strict';
+const { ObjectId } = require('mongoose').Types;
+const { CrudService } = require('naf-framework-mongoose/lib/service');
+class BillService extends CrudService {
+  constructor(ctx) {
+    super(ctx, 'bill');
+    this.model = this.ctx.model.Bill;
+  }
+
+}
+module.exports = BillService;

+ 462 - 22
app/service/order/order.js

@@ -1,6 +1,7 @@
 'use strict';
 
 const _ = require('lodash');
+const assert = require('assert');
 const { ObjectId } = require('mongoose').Types;
 const { CrudService } = require('naf-framework-mongoose/lib/service');
 const { BusinessError, ErrorCode } = require('naf-core').Error;
@@ -9,6 +10,7 @@ class OrderService extends CrudService {
   constructor(ctx) {
     super(ctx, 'order');
     this.model = this.ctx.model.Order;
+    this.util = this.ctx.service.util.util;
   }
   /**
    * 新添订单
@@ -46,8 +48,15 @@ class OrderService extends CrudService {
         if (sobj) {
           // 查找其有没有被拆分
           const r = split.find(f => ObjectId(f.pid).equals(sobj._id));
-          if (r) throw new BusinessError(ErrorCode.DATA_INVALID, '无法删除已被拆分过的货物');
-          const s_index = split.findIndex(f => ObjectId(f.pid).equals(og._id));
+          if (r) {
+            throw new BusinessError(
+              ErrorCode.DATA_INVALID,
+              '无法删除已被拆分过的货物'
+            );
+          }
+          const s_index = split.findIndex(f =>
+            ObjectId(f.pid).equals(og._id)
+          );
           split.splice(s_index, 1);
         }
         return undefined;
@@ -73,12 +82,6 @@ class OrderService extends CrudService {
     }
     res.goods = oldGoods;
     res.split = split;
-    // console.group('oldGoods');
-    // console.log(oldGoods);
-    // console.groupEnd();
-    // console.group('split');
-    // console.log(split);
-    // console.groupEnd();
     await this.model.update({ _id: ObjectId(id) }, info);
     await res.save();
     this.copyToSplit(id);
@@ -91,14 +94,16 @@ class OrderService extends CrudService {
   }
 
   /**
-   * 检查并处理有修改的货物:拆分就不允许修改了
+   * 检查并处理有修改的货物:拆分/发车就不允许修改了
    * @param {Array} oldGoods 查出修改前的货物列表
    * @param {Array} newGoods 修改后的货物列表
    * @param {Array} split 原拆分货物列表,用来检查是否可以修改
    */
   async goodsUpdate(oldGoods, newGoods, split) {
     oldGoods = oldGoods.map(og => {
-      const is_split = split.find(f => ObjectId(f.pid).equals(og._id) && f.type === '1');
+      const is_split = split.find(
+        f => ObjectId(f.pid).equals(og._id) && f.type === '1' && f.status === '0'
+      );
       if (is_split) return og;
       // 没有拆分,可以直接更换
       const ng = newGoods.find(f => ObjectId(f._id).equals(og._id));
@@ -152,7 +157,6 @@ class OrderService extends CrudService {
       }
     }
     await order.save();
-
   }
 
   /**
@@ -164,8 +168,15 @@ class OrderService extends CrudService {
   async sendGoods(goods, no, time) {
     for (const g of goods) {
       const { split_id, name, number, weight, volume } = g;
-      const order = await this.model.findOne({ 'split._id': ObjectId(split_id) });
-      if (!order) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, `未找到该${name}所在的订单信息`);
+      const order = await this.model.findOne({
+        'split._id': ObjectId(split_id),
+      });
+      if (!order) {
+        throw new BusinessError(
+          ErrorCode.DATA_NOT_EXIST,
+          `未找到该${name}所在的订单信息`
+        );
+      }
       const obj = { split_id, name, number, weight, volume, no, time };
       // 添加该货物的发货记录
       order.send_time.push(obj);
@@ -195,8 +206,15 @@ class OrderService extends CrudService {
   async arriveGoods(goods, no, time) {
     for (const g of goods) {
       const { split_id, name, number, weight, volume } = g;
-      const order = await this.model.findOne({ 'split._id': ObjectId(split_id) });
-      if (!order) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, `未找到该${name}所在的订单信息`);
+      const order = await this.model.findOne({
+        'split._id': ObjectId(split_id),
+      });
+      if (!order) {
+        throw new BusinessError(
+          ErrorCode.DATA_NOT_EXIST,
+          `未找到该${name}所在的订单信息`
+        );
+      }
       const obj = { split_id, name, number, weight, volume, no };
       if (time) obj.time = time;
       // 添加收货记录
@@ -237,8 +255,8 @@ class OrderService extends CrudService {
     // 检查是否有到达的
     const is_arrive = splitList.some(e => e.status === '-1');
     const word = [];
-    if (is_send)word.push('部分货物已发出');
-    if (is_arrive)word.push('部分货物已到达');
+    if (is_send) word.push('部分货物已发出');
+    if (is_arrive) word.push('部分货物已到达');
     if (word.length > 0) return word.join(';');
     return '状态错误';
   }
@@ -252,7 +270,11 @@ class OrderService extends CrudService {
     const res = await this.model.update({ _id: ObjectId(_id) }, { principal });
     try {
       // 跨库查询该用户姓名
-      const user = await this.ctx.service.httpUtil.cget(`/user/${principal}`, 'userAuth', { _tenant: 'zhwl' });
+      const user = await this.ctx.service.httpUtil.cget(
+        `/user/${principal}`,
+        'userAuth',
+        { _tenant: 'zhwl' }
+      );
       const message = `变更订单负责人 ${user.name}`;
       this.record(_id, { method: 'principal', message });
     } catch (error) {
@@ -261,7 +283,6 @@ class OrderService extends CrudService {
     return res;
   }
 
-
   /**
    * 订单操作记录
    * @param {String} id 订单id
@@ -269,10 +290,17 @@ class OrderService extends CrudService {
    */
   async record(id, { method, message }) {
     const order = await this.model.findById(id);
-    if (!order) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, `未找到订单,传入id:${id}`);
+    if (!order) {
+      throw new BusinessError(
+        ErrorCode.DATA_NOT_EXIST,
+        `未找到订单,传入id:${id}`
+      );
+    }
     const { authorization } = this.ctx.request.header;
     let user = decodeURI(authorization);
-    if (!user) { throw new BusinessError(ErrorCode.USER_NOT_EXIST, '未找到操作人'); }
+    if (!user) {
+      throw new BusinessError(ErrorCode.USER_NOT_EXIST, '未找到操作人');
+    }
     user = JSON.parse(user);
     const { id: userid, name: username } = user;
     const record = { opera: username, operaid: userid };
@@ -282,10 +310,422 @@ class OrderService extends CrudService {
     else if (method === 'in') record.message = `${username}修改收入`;
     else if (method === 'out') record.message = `${username}修改支出`;
     else if (method === 'split') record.message = `${username}拆分货物`;
-    else if (method === 'send' || method === 'arrive' || method === 'principal') record.message = `${message}`;
+    else if (
+      method === 'send' ||
+      method === 'arrive' ||
+      method === 'principal'
+    ) {
+      record.message = `${message}`;
+    } else if (method === 'js') record.message = `${username}结算订单`;
     order.record.push(record);
     await order.save();
   }
+
+  /**
+   * 客户结算查询
+   * @param {Object} query 查询条件
+   */
+  async clientCalculate(query) {
+    query = this.util.turnDateRangeQuery(this.util.turnFilter(query));
+    let list = await this.model
+      .$where('this.split.every(i=>i.status=== "-1")')
+      .find({ ...query, is_js: false })
+      .populate([
+        {
+          path: 'client',
+          model: 'Client',
+        },
+        {
+          path: 'item',
+          model: 'Item',
+        },
+        {
+          path: 'route',
+          model: 'Route',
+        },
+        {
+          path: 'treaty',
+          model: 'Treaty',
+        },
+        {
+          path: 'goods',
+          populate: [
+            {
+              path: 'mode',
+              model: 'Mode',
+            },
+          ],
+        },
+      ]);
+    if (list.length > 0) list = JSON.parse(JSON.stringify(list));
+    // 组织数据
+    list = this.toResetOrder(list);
+    return list;
+  }
+
+  /**
+   * 整合订单信息
+   * @param {Array} list 订单列表
+   */
+  toResetOrder(list) {
+    list = list.map(i => {
+      i = this.orderReset(i);
+      i = this.orderIn(i);
+      return i;
+    });
+    return list;
+  }
+
+  /**
+   * 将客户相关信息转换
+   * @param {Object} order 订单信息
+   */
+  orderReset(order) {
+    order = JSON.parse(JSON.stringify(order));
+    const { client, item, route, treaty } = order;
+    if (client && _.isObject(client)) {
+      order.client_id = _.get(client, '_id');
+      order.client = _.get(client, 'name');
+    }
+    if (item && _.isObject(item)) {
+      order.item_id = _.get(item, '_id');
+      order.item = _.get(item, 'name');
+    }
+    if (route && _.isObject(route)) {
+      order.route_id = _.get(route, '_id');
+      order.route = _.get(route, 'name');
+    }
+    if (treaty && _.isObject(treaty)) {
+      order.treaty_id = _.get(treaty, '_id');
+      order.treaty = _.get(treaty, 'number');
+    }
+    return order;
+  }
+
+  /**
+   * 计算收入总价
+   * @param {Object} order 订单信息
+   */
+  orderIn(order) {
+    const { goods, in_bill } = order;
+    const gi = goods.reduce((p, n) => p + (n.sh_ss || 0), 0);
+    const bi = in_bill.reduce((p, n) => p + (n.sh_ss || 0), 0);
+    const sh_ss = gi + bi;
+    order.sh_ss = sh_ss;
+    return order;
+  }
+
+  /**
+   * 选择指定的订单,导出收入excel
+   * @param {Object} query 查询条件
+   * @property Array ids 订单id集合
+   */
+  async clientExport(query) {
+    const { ids } = query;
+    console.log(ids);
+    let list = await this.model
+      .find({ _id: ids.map(i => ObjectId(i)) })
+      .populate([
+        {
+          path: 'client',
+          model: 'Client',
+        },
+        {
+          path: 'item',
+          model: 'Item',
+        },
+        {
+          path: 'route',
+          model: 'Route',
+        },
+        {
+          path: 'treaty',
+          model: 'Treaty',
+        },
+        {
+          path: 'goods',
+          populate: [
+            {
+              path: 'mode',
+              model: 'Mode',
+            },
+          ],
+        },
+      ]);
+    if (list.length > 0) list = JSON.parse(JSON.stringify(list));
+    // 处理信息
+    const arr = [];
+    // 获取头
+    const { header, otherList } = this.getHeader(list);
+    // 已经被占用的行数
+    let ouse = 1;
+    for (const order of list) {
+      // 计算这个订单的开始行和结束行位置
+      const os = ouse + 1; // 订单开始行数
+      const { goods } = order;
+      // 整理共同部分数据
+      const edata = this.getPublicExcelData(order, os);
+      // 再处理额外项的头数据
+      const odata = this.getOtherInExcelData(order, os, otherList);
+      arr.push({ ...edata, ...odata });
+      // 处理完后,更新被占用行数
+      ouse = ouse + goods.length;
+    }
+    //  处理头
+    let pkeys = Object.keys(header);
+    pkeys = pkeys.map(i => {
+      const reg = /[^a-zA-Z]/i;
+      const res = i.replace(reg, '');
+      return res;
+    });
+    let lastData = this.toResetExcelData(pkeys, arr);
+    lastData.push({ content: header });
+    const alignment = { vertical: 'middle', horizontal: 'center' };
+    lastData = lastData.map(i => ({ ...i, alignment }));
+    const res = await this.ctx.service.util.excel.toExcel({ data: lastData });
+    return res;
+  }
+
+  /**
+   * 合并处理数据
+   * @param {Array} keys 列字母数组
+   * @param {Array} data excel数据(不含头)
+   */
+  toResetExcelData(keys, data) {
+    const reg = /[^a-zA-Z]/i;
+    const arr = [];
+    const clear = [];
+    for (const key of keys) {
+      // 找出每列的内容
+      const col = data.map(i => {
+        const lks = Object.keys(i);
+        const r = lks.find(f => {
+          const rr = f.replace(reg, '');
+          return rr === key;
+        });
+        if (r) return { key: r, value: _.get(i, r) };
+      });
+      // 同一列满足以下条件可以合并:
+      // 1,值相同;2数字连贯;3指定范围内
+      // 先查范围
+      const head = _.head(col);
+      if (!head) continue;
+      const letter = head.key.replace(reg, '');
+      const r3 = this.mergeRange(letter);
+      if (!r3) continue;
+      const l = col.length;
+      const ul = _.uniqBy(col, 'value').length;
+      if (ul === l) continue;
+      // 可以合并,需要重新拼个Object,{scell,ecell,content}
+      // scell 是上面head的key, ecell是last获取key,content,随意拿出一个就行
+      const obj = {};
+      obj.scell = _.get(head, 'key');
+      obj.ecell = _.get(_.last(col), 'key');
+      obj.content = _.get(head, 'value');
+      clear.push(_.get(head, 'key'), _.get(_.last(col), 'key'));
+      arr.push(obj);
+    }
+    // 将scell和ecell都干掉
+    data = data.map(i => {
+      i = _.omitBy(i, (value, key) => {
+        return clear.includes(key);
+      });
+      return { content: i };
+    });
+    data = [ ...data, ...arr ];
+    return data;
+  }
+
+  /**
+   * 查询该列是否可以合并
+   * @param {String} letter 字母
+   */
+  mergeRange(letter) {
+    const arr = [ 'U', 'Z', 'AA', 'AB', 'AC' ];
+    return !arr.includes(letter);
+  }
+  /**
+   * 整理成excel数据(先整理固定部分,即不包含额外收入部分)
+   * @param {Object} order 订单信息
+   * @param {Number} os 该订单开始行数
+   */
+  getPublicExcelData(order, os) {
+    // 如果不需要合并,那一个订单也就是1个object,如果需要合并,那就是多个object,
+    // 即使是多个object也没问题,因为控制的是每个单元格,所以出问题一定是单元格没弄明白
+    // 除了货物,方式外,其他的固定项都需要判断是否需要合并单元格,所以先处理货物,方式
+    const arr = [];
+    const content = {};
+    const { goods } = order;
+    // 货物,方式部分
+    for (let i = 0; i < goods.length; i++) {
+      const good = goods[i];
+      const {
+        mode,
+        name,
+        costname = '运费',
+        sq_ys,
+        sq_ss,
+        sh_ys,
+        sh_ss,
+      } = good;
+      const { name: modeName, price, is_lf, send_type, computed_type } = mode;
+      content[`S${i + os}`] = modeName;
+      content[`T${i + os}`] = price;
+      content[`U${i + os}`] = name;
+      content[`V${i + os}`] = costname;
+      content[`W${i + os}`] = is_lf ? '是' : '否';
+      content[`X${i + os}`] = send_type;
+      content[`Y${i + os}`] = computed_type;
+      content[`Z${i + os}`] = sq_ys;
+      content[`AA${i + os}`] = sq_ss;
+      content[`AB${i + os}`] = sh_ys;
+      content[`AC${i + os}`] = sh_ss;
+      // 订单
+      content[`A${os}`] = _.get(order, 'order_no');
+      content[`AD${os}`] = _.get(order, 'remark');
+      // 客户部分一定合并,不在这里处理
+      content[`B${os}`] = _.get(order.client, 'name');
+      content[`C${os}`] = _.get(order.client, 'address');
+      content[`D${os}`] = _.get(order.client, 'legal');
+      content[`E${os}`] = _.get(order.client, 'mobile');
+      content[`F${os}`] = _.get(order.client, 'taxes_no');
+      content[`G${os}`] = _.get(order.client, 'account_bank');
+      content[`H${os}`] = _.get(order.client, 'account');
+      // 合同部分
+      content[`I${os}`] = _.get(order.treaty, 'number');
+      content[`J${os}`] = _.get(order.treaty, 'jf');
+      content[`K${os}`] = _.get(order.treaty, 'yf');
+      content[`L${os}`] = _.get(order.treaty, 'period');
+      content[`M${os}`] = _.get(order.treaty, 'settle_up');
+      // 项目
+      content[`N${os}`] = _.get(order.item, 'name');
+      content[`O${os}`] = _.get(order.item, 'taxes');
+      // 线路
+      content[`P${os}`] = _.get(order.route, 'name');
+      content[`Q${os}`] = _.get(order.route, 's_p');
+      content[`R${os}`] = _.get(order.route, 'e_p');
+    }
+    arr.push(content);
+    return content;
+  }
+
+  /**
+   * 填充额外收入项
+   * @param {Object} order 订单信息
+   * @param {Number} os 该订单开始行数
+   * @param {Array} inList 额外收入项
+   */
+  getOtherInExcelData(order, os, inList) {
+    const obj = {};
+    const { in_bill, goods } = order;
+    for (let i = 0; i < goods.length; i++) {
+      for (const oin of inList) {
+        const { key, item, value } = oin;
+        const r = in_bill.find(f => f.item === item);
+        // console.log(key, item, value, r);
+        if (r) {
+          obj[`${key}${i + os}`] = _.get(r, value);
+        }
+      }
+    }
+    return obj;
+  }
+
+  /**
+   * 获取导出的订单头
+   * @param {Array} list 选择的订单
+   */
+  getHeader(list) {
+    const obj = {};
+    // 同一客户下:B-H一定是合并的
+    // 同一订单下: A,AD1一定是合并的
+    // 订单部分
+    obj.A1 = '订单编号';
+    // 客户部分
+    obj.B1 = '客户名称';
+    obj.C1 = '地址';
+    obj.D1 = '法人';
+    obj.E1 = '联系电话';
+    obj.F1 = '税号';
+    obj.G1 = '开户行';
+    obj.H1 = '银行账号';
+    // 合同部分
+    obj.I1 = '合同编号';
+    obj.J1 = '甲方';
+    obj.K1 = '乙方';
+    obj.L1 = '合同周期';
+    obj.M1 = '结算方式';
+    // 项目部分
+    obj.N1 = '项目名称';
+    obj.O1 = '税率';
+    // 线路部分
+    obj.P1 = '线路名称';
+    obj.Q1 = '起始地';
+    obj.R1 = '目的地';
+    // 方式部分
+    obj.S1 = '方式名称';
+    obj.T1 = '价格';
+    // 货物部分
+    obj.U1 = '货物名称';
+    obj.V1 = '费用名称'; // 固定全是运费
+    // 方式部分
+    obj.W1 = '量份收费'; // 需要换成 是/否
+    obj.X1 = '发货方式';
+    obj.Y1 = '计费方式';
+    // 货物部分
+    obj.Z1 = '税前应收';
+    obj.AA1 = '税前实收';
+    obj.AB1 = '税后应收';
+    obj.AC1 = '税后实收';
+    // 订单部分
+    obj.AD1 = '订单备注';
+    // 处理额外收入头部
+    const in_item = _.uniqBy(list.map(i => i.in_bill).flat(), 'item');
+    const arr = [];
+    let i = 31;
+    for (const inInfo of in_item) {
+      const { item } = inInfo;
+      const fl = this.ctx.service.util.excel.numberToLetter(_.floor(i / 26));
+      // const sl = this.ctx.service.util.excel.numberToLetter(i % 26);
+      const sqyl = this.ctx.service.util.excel.numberToLetter(i % 26);
+      obj[`${fl}${sqyl}1`] = `${item}税前应收`;
+      arr.push({ key: `${fl}${sqyl}`, item, value: 'sq_ys' });
+      i++;
+      const sqsl = this.ctx.service.util.excel.numberToLetter(i % 26);
+      obj[`${fl}${sqsl}1`] = `${item}税前实收`;
+      arr.push({ key: `${fl}${sqsl}`, item, value: 'sq_ss' });
+      i++;
+      const shyl = this.ctx.service.util.excel.numberToLetter(i % 26);
+      obj[`${fl}${shyl}1`] = `${item}税后应收`;
+      arr.push({ key: `${fl}${shyl}`, item, value: 'sh_ys' });
+      i++;
+      const shsl = this.ctx.service.util.excel.numberToLetter(i % 26);
+      obj[`${fl}${shsl}1`] = `${item}税后实收`;
+      arr.push({ key: `${fl}${shsl}`, item, value: 'sh_ss' });
+      i++;
+    }
+    return { header: obj, otherList: arr };
+  }
+
+  /**
+   * 订单结算
+   * @param {Object} {ids} 要结算的订单
+   */
+  async js({ ids, client, owner }) {
+    assert(ids, '缺少订单信息');
+    assert(client, '缺少客户信息');
+    assert(owner, '缺少创建人信息');
+    const bill = await this.ctx.model.Bill.create({ ids, client, owner });
+    if (!bill) throw new BusinessError(ErrorCode.DATABASE_FAULT, '结算单创建失败');
+    const res = await this.model.updateMany({ _id: ids.map(i => ObjectId(i)) }, { is_js: true });
+    try {
+      for (const id of ids) {
+        this.record(id, { method: 'js' });
+      }
+    } catch (error) {
+      this.logger.error(`订单id:${res.id}记录创建失败:${error.toString()}`);
+    }
+  }
 }
 
 module.exports = OrderService;

+ 1 - 1
app/service/order/transport.js

@@ -193,7 +193,7 @@ class TransportService extends CrudService {
     const text = { vertAlign: 'superscript', size: 18 };
     const alignment = { vertical: 'middle', horizontal: 'center' };
     arr = arr.map(i => {
-      i.alignment = i.alignment = { ...alignment, ..._.get(i, alignment, {}) };
+      i.alignment = { ...alignment, ..._.get(i, alignment, {}) };
       i.font = { ...text, ..._.get(i, 'font', {}) };
       return i;
     });

+ 3 - 1
app/service/util/excel.js

@@ -45,6 +45,8 @@ class ExcelService extends CrudService {
       sheet.columns = meta;
       sheet.addRows(data);
     } else {
+      // 需要合并,每行数据就有scell,ecell,content为合并后的内容
+      // 不需要合并,就没有scell,ecell,content是Object,以 单元格位置(A1):内容 的key:value形式保存
       for (const row of data) {
         const { scell, ecell, content, alignment, font } = row;
         if (scell && ecell) {
@@ -74,7 +76,7 @@ class ExcelService extends CrudService {
   }
 
   numberToLetter(num) {
-    const arr = [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'Y', 'Z' ];
+    const arr = [ 'Z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'Y' ];
     return arr[num];
   }
 }

+ 48 - 3
app/service/util/util.js

@@ -7,7 +7,9 @@ class UtilService extends CrudService {
     super(ctx);
     this.mq = this.ctx.mq;
   }
-
+  async utilMethod(query, body) {
+    console.log(query, body);
+  }
   /**
    * 获取字段前置函数
    * @param {String} param0 {model字段名
@@ -71,8 +73,51 @@ class UtilService extends CrudService {
     };
     return obj[_.lowerCase(model)];
   }
-  async utilMethod(query, body) {
-    console.log(query, body);
+  /**
+   * 将查询条件中模糊查询的标识转换成对应object
+   * @param {Object} filter 查询条件
+   */
+  turnFilter(filter) {
+    const str = /^%\w*%$/;
+    const keys = Object.keys(filter);
+    for (const key of keys) {
+      const res = key.match(str);
+      if (res) {
+        const newKey = key.slice(1, key.length - 1);
+        filter[newKey] = new RegExp(filter[key]);
+        delete filter[key];
+      }
+    }
+    return filter;
+  }
+  /**
+   * 将时间转换成对应查询Object
+   * @param {Object} filter 查询条件
+   */
+  turnDateRangeQuery(filter) {
+    const keys = Object.keys(filter);
+    for (const k of keys) {
+      if (k.includes('@')) {
+        const karr = k.split('@');
+        //  因为是针对处理范围日期,所以必须只有,开始时间和结束时间
+        if (karr.length === 2) {
+          const type = karr[1];
+          if (type === 'start') {
+            filter[karr[0]] = {
+              ..._.get(filter, karr[0], {}),
+              $gte: filter[k],
+            };
+          } else {
+            filter[karr[0]] = {
+              ..._.get(filter, karr[0], {}),
+              $lte: filter[k],
+            };
+          }
+          delete filter[k];
+        }
+      }
+    }
+    return filter;
   }
 }
 module.exports = UtilService;