8个文件已删除
33个文件已添加
12个文件已修改
1460 ■■■■■ 已修改文件
src/modules/base/config.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/basicdata/controller/admin/site.ts 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/basicdata/controller/admin/sorter.ts 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/basicdata/entity/sorter.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/basicdata/service/iot.ts 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/basicdata/service/site.ts 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/basicdata/service/sorter.ts 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/monitor/service/capturetask.ts 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/push/controller/admin/sorteralarm.ts 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/push/controller/admin/sorterattendance.ts 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/push/controller/admin/sortergps.ts 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/push/controller/admin/sorterhealth.ts 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/push/controller/app/open.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/push/entity/sorteralarm.ts 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/push/entity/sorterattendance.ts 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/push/entity/sortergps.ts 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/push/entity/sorterhealth.ts 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/push/service/sorteralarm.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/push/service/sorterattendance.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/push/service/sortergps.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/push/service/sorterhealth.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/shop/config.ts 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/shop/controller/admin/recycleappointment.ts 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/shop/controller/admin/recycleappointmentlog.ts 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/shop/controller/admin/recycleitem.ts 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/shop/controller/admin/recycleorder.ts 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/shop/controller/admin/recycleorderitem.ts 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/shop/controller/admin/transaction.ts 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/shop/controller/app/recycleorder.ts 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/shop/controller/app/recycleorderitem.ts 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/shop/entity/recycleappointment.ts 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/shop/entity/recycleappointmentlog.ts 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/shop/entity/recycleitem.ts 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/shop/entity/recycleorder.ts 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/shop/entity/recycleorderitem.ts 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/shop/entity/transaction.ts 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/shop/service/recycleappointment.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/shop/service/recycleappointmentlog.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/shop/service/recycleitem.ts 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/shop/service/recycleorder.ts 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/shop/service/recycleorderitem.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/shop/service/transaction.ts 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/user/controller/admin/info.ts 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/user/entity/info.ts 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/user/service/info.ts 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
uploads/capture/20260810/GT7568358_1786354561815.jpg 补丁 | 查看 | 原始文档 | blame | 历史
uploads/capture/20260810/GT7568358_1786354599954.jpg 补丁 | 查看 | 原始文档 | blame | 历史
uploads/capture/20260810/GU7398831_1786354575450.jpg 补丁 | 查看 | 原始文档 | blame | 历史
uploads/capture/20260810/GV6662505_1786354596817.jpg 补丁 | 查看 | 原始文档 | blame | 历史
uploads/capture/20260810/GV6662514_1786354589019.jpg 补丁 | 查看 | 原始文档 | blame | 历史
uploads/capture/20260810/GV7474259_1786354585367.jpg 补丁 | 查看 | 原始文档 | blame | 历史
uploads/capture/20260810/GV7474275_1786354579899.jpg 补丁 | 查看 | 原始文档 | blame | 历史
uploads/capture/20260810/GV7474291_1786354592342.jpg 补丁 | 查看 | 原始文档 | blame | 历史
src/modules/base/config.ts
@@ -31,7 +31,7 @@
      // token
      token: {
        // 2小时过期,需要用刷新token
        expire: 2 * 3600,
        expire: 7 * 24 * 3600,
        // 15天内,如果没操作过就需要重新登录
        refreshExpire: 24 * 3600 * 15,
      },
src/modules/basicdata/controller/admin/site.ts
@@ -2,18 +2,13 @@
import { BasicdataSiteEntity } from '../../entity/site';
import { BasicdataSiteService } from '../../service/site';
import { BaseSysDepartmentEntity } from '../../../base/entity/sys/department';
import { WithDeptFilterWhere } from '../../../base/middleware/with-dept-filter-where'
/**
 * 站点信息后台管理
 */
@CoolController({
  api: ['add', 'delete', 'update', 'info', 'list', 'page'],
  entity: BasicdataSiteEntity,
  service: BasicdataSiteService,
  pageQueryOp: {
    keyWordLikeFields: ['a.name', 'a.address', 'a.leader'],
    fieldEq: ['a.departmentId', 'a.siteType', 'a.status'],
    // ✅ page 方法已在 Service 中重写,这里配置仅作兜底
    select: ['a.*', 'b.name AS departmentName'],
    join: [
      {
@@ -23,15 +18,6 @@
        type: 'leftJoin',
      },
    ],
    where: async ctx => {
      const conditions: any[][] = [];
      conditions.push(...(await WithDeptFilterWhere(ctx, { alias: 'a', field: 'departmentId' })));
      // 其他关联表(举例)
      // conditions.push(...(await deptFilterWhere(ctx, 'd', 'id')));
      return conditions;
    },
  },
})
export class AdminBasicdataSiteController extends BaseController { }
export class AdminBasicdataSiteController extends BaseController {}
src/modules/basicdata/controller/admin/sorter.ts
@@ -39,10 +39,7 @@
    where: async ctx => {
      const conditions: any[][] = [];
      conditions.push(...(await WithDeptFilterWhere(ctx, { alias: 'a', field: 'departmentId' })));
      // 其他关联表(举例)
      // conditions.push(...(await deptFilterWhere(ctx, 'd', 'id')));
      return conditions;
    },
  },
src/modules/basicdata/entity/sorter.ts
@@ -13,12 +13,24 @@
  @Column({ comment: '姓名', length: 64 })
  businessName: string;
  @Column({ comment: '手机号', length: 32, nullable: true })
  @Column({ comment: '手机号', length: 32 })
  phone: string;
  @Column({ comment: '密码', length: 32, nullable: true })
  password: string;
  @Column({ comment: '性别', dict: ['未知', '男', '女'], default: '男' })
  sex: string;
  @Column({ comment: '生日', type: 'date', nullable: true })
  birthday: Date;
  @Column({ comment: '入职日期', type: 'date', nullable: true })
  hireDate: Date;
  @Column({ comment: '离职日期', type: 'date', nullable: true })
  leaveDate: Date;
  @Column({ comment: '状态', dict: ['在职', '离职', '禁用'], default: 0 })
  status: number;
src/modules/basicdata/service/iot.ts
@@ -114,4 +114,24 @@
    return result;
  }
  /**
   * 递归获取部门及其所有子部门 ID
   */
  async getDepartmentAndChildren(deptId: number): Promise<number[]> {
    const result: number[] = [deptId];
    const findChildren = async (pid: number) => {
      const children = await this.baseSysDepartmentEntity
        .createQueryBuilder('d')
        .select('d.id')
        .where('d.parentId = :pid', { pid })
        .getMany();
      for (const c of children) {
        result.push(c.id);
        await findChildren(c.id);
      }
    };
    await findChildren(deptId);
    return result;
  }
}
src/modules/basicdata/service/site.ts
@@ -1,14 +1,112 @@
import { Provide } from '@midwayjs/core';
import { Provide, Inject, Context } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { BasicdataSiteEntity } from '../entity/site';
import { BaseSysDepartmentEntity } from '../../base/entity/sys/department';
import { WithDeptFilterWhere } from '../../base/middleware/with-dept-filter-where';
import { Context as KoaContext } from '@midwayjs/koa';
/**
 * 站点服务
 */
@Provide()
export class BasicdataSiteService extends BaseService {
  @InjectEntityModel(BasicdataSiteEntity)
  basicdataSiteEntity: Repository<BasicdataSiteEntity>;
}
  @InjectEntityModel(BaseSysDepartmentEntity)
  baseSysDepartmentEntity: Repository<BaseSysDepartmentEntity>;
  @Inject()
  ctx: KoaContext;
  /**
   * 重写 page —— 显式列出字段,避免 a.* 的别名问题
   */
  async page(query: any) {
    const {
      page = 1,
      size = 20,
      departmentId,
      siteType,
      status,
      keyWord,
      sort,
      order,
    } = query;
    const qb = this.basicdataSiteEntity
      .createQueryBuilder('a')
      .leftJoin(BaseSysDepartmentEntity, 'b', 'a.departmentId = b.id')
      .select([
        'a.id',
        'a.businessId',
        'a.businessName',
        'a.departmentId',
        'a.siteType',
        'a.address',
        'a.leader',
        'a.longitude',
        'a.latitude',
        'a.photo',
        'a.status',
        'a.remark',
        'a.createTime',
        'a.updateTime',
        'b.name AS departmentName',
      ]);
    const authConds = await WithDeptFilterWhere(this.ctx, {
      alias: 'a',
      field: 'departmentId',
    });
    for (const [sql, params] of authConds) {
      qb.andWhere(sql, params);
    }
    if (departmentId) {
      const deptIds = await this.getDepartmentAndChildren(Number(departmentId));
      qb.andWhere('a.departmentId IN (:deptIds)', { deptIds });
    }
    if (siteType !== undefined && siteType !== '') {
      qb.andWhere('a.siteType = :siteType', { siteType });
    }
    if (status !== undefined && status !== '') {
      qb.andWhere('a.status = :status', { status: Number(status) });
    }
    if (keyWord) {
      qb.andWhere(
        '(a.businessId LIKE :kw OR a.businessName LIKE :kw OR a.address LIKE :kw OR a.leader LIKE :kw)',
        { kw: `%${keyWord}%` }
      );
    }
    const sortField = order || 'createTime';
    const sortDir = sort === 'asc' ? 'ASC' : 'DESC';
    qb.orderBy(`a.${sortField}`, sortDir);
    const skip = (Number(page) - 1) * Number(size);
    const [list, total] = await qb
      .offset(skip)
      .limit(Number(size))
      .getManyAndCount();
    return {
      list,
      pagination: { page: Number(page), size: Number(size), total },
    };
  }
  /**
   * 递归获取部门及其所有子部门 ID
   */
  async getDepartmentAndChildren(deptId: number): Promise<number[]> {
    const result: number[] = [deptId];
    const findChildren = async (pid: number) => {
      const children = await this.baseSysDepartmentEntity
        .createQueryBuilder('d')
        .select('d.id')
        .where('d.parentId = :pid', { pid })
        .getMany();
      for (const c of children) {
        result.push(c.id);
        await findChildren(c.id);
      }
    };
    await findChildren(deptId);
    return result;
  }
}
src/modules/basicdata/service/sorter.ts
@@ -3,6 +3,7 @@
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { BasicdataSorterEntity } from '../entity/sorter';
import * as md5 from 'md5';
/**
 * 分拣人员服务
@@ -11,4 +12,13 @@
export class BasicdataSorterService extends BaseService {
  @InjectEntityModel(BasicdataSorterEntity)
  basicdataSorterEntity: Repository<BasicdataSorterEntity>;
   /**
     * 修改之前处理密码加密
     */
    async modifyBefore(data: any, type: 'delete' | 'update' | 'add') {
      if ((type === 'add' || type === 'update') && data.password) {
        data.password = md5(data.password);
      }
    }
}
src/modules/monitor/service/capturetask.ts
@@ -108,7 +108,7 @@
                // 4. 本地目录
                const dateDir = new Date().toISOString().slice(0, 10).replace(/-/g, '');
                const localDir = path.join(process.cwd(), 'uploads', 'capture', dateDir);
                const localDir = path.join(process.cwd(), 'upload', 'capture', dateDir);
                fs.mkdirSync(localDir, { recursive: true });
                console.log('[Capture] 本地目录=', localDir);
@@ -121,9 +121,9 @@
                console.log('[Capture] 图片下载完成');
                const relativePath = path
                    .relative(path.join(process.cwd(), 'uploads'), dest)
                    .relative(path.join(process.cwd(), 'upload'), dest)
                    .replace(/\\/g, '/');
                const localUrl = `/uploads/${relativePath}`;
                const localUrl = `/upload/${relativePath}`;
                console.log('[Capture] localUrl=', localUrl);
                const entity = {
@@ -278,7 +278,7 @@
        const spaceId = await this.getSpaceID();
        const imgs = await this.queryCaptureImages(deviceSerial, spaceId);
        const dateDir = new Date().toISOString().slice(0, 10).replace(/-/g, '');
        const localDir = path.join(process.cwd(), 'uploads', 'capture', dateDir);
        const localDir = path.join(process.cwd(), 'upload', 'capture', dateDir);
        fs.mkdirSync(localDir, { recursive: true });
        for (const img of imgs) {
src/modules/push/controller/admin/sorteralarm.ts
New file
@@ -0,0 +1,36 @@
import { CoolController, BaseController } from '@cool-midway/core';
import { PushSorterAlarmEntity } from '../../entity/sorteralarm';
import { PushSorterAlarmService } from '../../service/sorteralarm';
import { BaseSysDepartmentEntity } from '../../../base/entity/sys/department'
import { WithDeptFilterWhere } from '../../../base/middleware/with-dept-filter-where'
/**
 * 分拣员轨迹管理
 */
@CoolController({
  api: ['add', 'delete', 'update', 'info', 'list', 'page'],
  entity: PushSorterAlarmEntity,
  service: PushSorterAlarmService,
  pageQueryOp: {
    select: ['a.*', 'b.name as departmentName'],
    join: [
      {
        entity: BaseSysDepartmentEntity,
        alias: 'b',
        condition: 'a.departmentId = b.id',
        type: 'leftJoin',
      },
    ],
    fieldEq: ['a.sorterId', 'a.departmentId'],
    where: async ctx => {
      const { startTime, endTime } = ctx.request.body;
      const conditions:any[][] = [];
      if (startTime && endTime) {
        conditions.push(['a.uploadTime BETWEEN :startTime AND :endTime', { startTime, endTime }]);
      }
      conditions.push(...(await WithDeptFilterWhere(ctx, { alias: 'a', field: 'departmentId' })));
      return conditions;
    },
  },
})
export class PushSorterAlarmController extends BaseController {}
src/modules/push/controller/admin/sorterattendance.ts
New file
@@ -0,0 +1,36 @@
import { CoolController, BaseController } from '@cool-midway/core';
import { PushSorterAttendanceEntity } from '../../entity/sorterattendance';
import { PushSorterAttendanceService } from '../../service/sorterattendance';
import { BaseSysDepartmentEntity } from '../../../base/entity/sys/department'
import { WithDeptFilterWhere } from '../../../base/middleware/with-dept-filter-where'
/**
 * 分拣员轨迹管理
 */
@CoolController({
  api: ['add', 'delete', 'update', 'info', 'list', 'page'],
  entity: PushSorterAttendanceEntity,
  service: PushSorterAttendanceService,
  pageQueryOp: {
    select: ['a.*', 'b.name as departmentName'],
    join: [
      {
        entity: BaseSysDepartmentEntity,
        alias: 'b',
        condition: 'a.departmentId = b.id',
        type: 'leftJoin',
      },
    ],
    fieldEq: ['a.sorterId', 'a.departmentId'],
    where: async ctx => {
      const { startTime, endTime } = ctx.request.body;
      const conditions:any[][] = [];
      if (startTime && endTime) {
        conditions.push(['a.uploadTime BETWEEN :startTime AND :endTime', { startTime, endTime }]);
      }
      conditions.push(...(await WithDeptFilterWhere(ctx, { alias: 'a', field: 'departmentId' })));
      return conditions;
    },
  },
})
export class PushSorterAttendanceController extends BaseController {}
src/modules/push/controller/admin/sortergps.ts
New file
@@ -0,0 +1,36 @@
import { CoolController, BaseController } from '@cool-midway/core';
import { PushSorterGpsEntity } from '../../entity/sortergps';
import { PushSorterGpsService } from '../../service/sortergps';
import { BaseSysDepartmentEntity } from '../../../base/entity/sys/department'
import { WithDeptFilterWhere } from '../../../base/middleware/with-dept-filter-where'
/**
 * 分拣员轨迹管理
 */
@CoolController({
  api: ['add', 'delete', 'update', 'info', 'list', 'page'],
  entity: PushSorterGpsEntity,
  service: PushSorterGpsService,
  pageQueryOp: {
    select: ['a.*', 'b.name as departmentName'],
    join: [
      {
        entity: BaseSysDepartmentEntity,
        alias: 'b',
        condition: 'a.departmentId = b.id',
        type: 'leftJoin',
      },
    ],
    fieldEq: ['a.sorterId', 'a.departmentId'],
    where: async ctx => {
      const { startTime, endTime } = ctx.request.body;
      const conditions:any[][] = [];
      if (startTime && endTime) {
        conditions.push(['a.uploadTime BETWEEN :startTime AND :endTime', { startTime, endTime }]);
      }
      conditions.push(...(await WithDeptFilterWhere(ctx, { alias: 'a', field: 'departmentId' })));
      return conditions;
    },
  },
})
export class PushSorterGpsController extends BaseController {}
src/modules/push/controller/admin/sorterhealth.ts
New file
@@ -0,0 +1,36 @@
import { CoolController, BaseController } from '@cool-midway/core';
import { PushSorterHealthEntity } from '../../entity/sorterhealth';
import { PushSorterHealthService } from '../../service/sorterhealth';
import { BaseSysDepartmentEntity } from '../../../base/entity/sys/department'
import { WithDeptFilterWhere } from '../../../base/middleware/with-dept-filter-where'
/**
 * 分拣员轨迹管理
 */
@CoolController({
  api: ['add', 'delete', 'update', 'info', 'list', 'page'],
  entity: PushSorterHealthEntity,
  service: PushSorterHealthService,
  pageQueryOp: {
    select: ['a.*', 'b.name as departmentName'],
    join: [
      {
        entity: BaseSysDepartmentEntity,
        alias: 'b',
        condition: 'a.departmentId = b.id',
        type: 'leftJoin',
      },
    ],
    fieldEq: ['a.sorterId', 'a.departmentId'],
    where: async ctx => {
      const { startTime, endTime } = ctx.request.body;
      const conditions:any[][] = [];
      if (startTime && endTime) {
        conditions.push(['a.uploadTime BETWEEN :startTime AND :endTime', { startTime, endTime }]);
      }
      conditions.push(...(await WithDeptFilterWhere(ctx, { alias: 'a', field: 'departmentId' })));
      return conditions;
    },
  },
})
export class PushSorterHealthController extends BaseController {}
src/modules/push/controller/app/open.ts
@@ -116,7 +116,7 @@
      this.ctx.body = { messageId: '' };
      return;
    }
    console.log('【萤石云原始报文】:', rawBody.slice(0, 500));
    console.log('【萤石云原始报文】:', rawBody);
    let msg: any;
    try {
      msg = JSON.parse(rawBody);
src/modules/push/entity/sorteralarm.ts
New file
@@ -0,0 +1,43 @@
import { BaseEntity } from '../../base/entity/base';
import { Column, Entity, Index } from 'typeorm';
/**
 * 分拣员健康告警信息
 */
@Entity('t_push_sorter_alarm')
export class PushSorterAlarmEntity extends BaseEntity {
    @Index()
    @Column({ comment: '身份证号', length: 18 })
    idCard: string;
    @Column({ comment: '所属单位' })
    departmentId: number;
    departmentName: string;
    @Index()
    @Column({ comment: '数据时间' })
    uploadTime: Date;
    @Column({ comment: '报警类型' })
    alarmType: string;
    @Column({ comment: '报警内容', type: 'text', nullable: true })
    content: string;
    @Column({ comment: '报警级别', dict: ['一般', '中等', '严重'], default: '一般' })
    alarmLevel: string;
    @Column({ comment: '处理状态', dict: ['未处理', '已处理'], default: 0 })
    status: number;
    @Column({ comment: '处理人ID', nullable: true })
    handleUser: number;
    @Column({ comment: '处理时间', nullable: true })
    handleTime: Date;
}
src/modules/push/entity/sorterattendance.ts
New file
@@ -0,0 +1,45 @@
import { BaseEntity } from '../../base/entity/base';
import { Column, Entity, Index } from 'typeorm';
/**
 * 分拣员签到,签退信息
 */
@Entity('t_push_sorter_attendance')
export class PushSorterAttendanceEntity extends BaseEntity {
    @Index()
    @Column({ comment: '身份证号', length: 18 })
    idCard: string;
    @Column({ comment: '所属单位' })
    departmentId: number;
    departmentName: string;
    @Index()
    @Column({ comment: '数据时间' })
    uploadTime: Date;
    @Index()
    @Column({ comment: '工作日期', type: 'date' })
    workDate: Date;
    @Column({ comment: '签到时间', nullable: true })
    checkInTime: Date;
    @Column({ comment: '签退时间', nullable: true })
    checkOutTime: Date;
    @Column({ comment: '签到地点', nullable: true })  // lon,lat
    checkInLocation: string;
    @Column({ comment: '签退地点', nullable: true }) // lon,lat
    checkOutLocation: string;
    @Column({ comment: '工作时长', type: 'decimal', precision: 4, scale: 2, default: 0.00 })
    workHours: number;
    @Column({ comment: '考勤状态', dict: ['正常', '迟到', '早退', '缺卡', '旷工'], default: '缺卡' })
    status: string;
}
src/modules/push/entity/sortergps.ts
New file
@@ -0,0 +1,42 @@
import { BaseEntity } from '../../base/entity/base';
import { Column, Entity, Index } from 'typeorm';
/**
 * 分拣员轨迹信息
 */
@Entity('t_push_sorter_gps')
export class PushSorterGpsEntity extends BaseEntity {
    @Index()
    @Column({ comment: '身份证号', length: 18 })
    idCard: string;
    @Column({ comment: '姓名', length: 18 })
    name: string;
    @Column({ comment: '所属单位' })
    departmentId: number;
    departmentName: string;
    @Index()
    @Column({ comment: '数据时间' })
    uploadTime: Date;
    @Column({ comment: '经度', type: 'decimal', precision: 10, scale: 7 })
    longitude: number;
    @Column({ comment: '纬度', type: 'decimal', precision: 10, scale: 7 })
    latitude: number;
    @Column({ comment: '高程', type: 'decimal', precision: 10, scale: 7 })
    height: number;
    @Column({ comment: '速度', type: 'decimal', precision: 5, scale: 2 })
    speed: number;
    @Column({ comment: '精度', type: 'decimal', precision: 5, scale: 2 })
    accuracy: number;
}
src/modules/push/entity/sorterhealth.ts
New file
@@ -0,0 +1,40 @@
import { BaseEntity } from '../../base/entity/base';
import { Column, Entity, Index } from 'typeorm';
/**
 * 分拣员健康监测信息
 */
@Entity('t_push_sorter_health')
export class PushSorterHealthEntity extends BaseEntity {
    @Index()
    @Column({ comment: '身份证号', length: 18 })
    idCard: string;
    @Column({ comment: '所属单位' })
    departmentId: number;
    departmentName: string;
    @Index()
    @Column({ comment: '数据时间' })
    uploadTime: Date;
    @Column({ comment: '体温', type: 'decimal', precision: 3, scale: 1 })
    temperature: number;
    @Column({ comment: '心率' })
    heartRate: number;
    @Column({ comment: '收缩压' })
    bloodPressureHigh: number;
    @Column({ comment: '舒张压' })
    bloodPressureLow: number;
    @Column({ comment: '血氧饱和度', type: 'decimal', precision: 4, scale: 1 })
    bloodOxygen: number;
    // add more if needed
}
src/modules/push/service/sorteralarm.ts
New file
@@ -0,0 +1,14 @@
import { Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { PushSorterAlarmEntity } from '../entity/sorteralarm';
/**
 * 分拣员健康告警服务
 */
@Provide()
export class PushSorterAlarmService extends BaseService {
  @InjectEntityModel(PushSorterAlarmEntity)
  pushSorterAlarmEntity: Repository<PushSorterAlarmEntity>;
}
src/modules/push/service/sorterattendance.ts
New file
@@ -0,0 +1,14 @@
import { Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { PushSorterAttendanceEntity } from '../entity/sorterattendance';
/**
 * 分拣员健康告警服务
 */
@Provide()
export class PushSorterAttendanceService extends BaseService {
  @InjectEntityModel(PushSorterAttendanceEntity)
  pushSorterAttendanceEntity: Repository<PushSorterAttendanceEntity>;
}
src/modules/push/service/sortergps.ts
New file
@@ -0,0 +1,14 @@
import { Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { PushSorterGpsEntity } from '../entity/sortergps';
/**
 * 分拣员轨迹服务
 */
@Provide()
export class PushSorterGpsService extends BaseService {
  @InjectEntityModel(PushSorterGpsEntity)
  pushSorterGpsEntity: Repository<PushSorterGpsEntity>;
}
src/modules/push/service/sorterhealth.ts
New file
@@ -0,0 +1,14 @@
import { Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { PushSorterHealthEntity } from '../entity/sorterhealth';
/**
 * 分拣员健康监测服务
 */
@Provide()
export class PushSorterHealthService extends BaseService {
  @InjectEntityModel(PushSorterHealthEntity)
  pushSorterHealthEntity: Repository<PushSorterHealthEntity>;
}
src/modules/shop/config.ts
New file
@@ -0,0 +1,16 @@
import { ModuleConfig } from '@cool-midway/core';
/**
 * 商城模块配置
 */
export default () => {
  return {
    name: '商城模块',
    description: 'user管理,垃圾种类,可回收物,预约订单,商城商品,谈积分账户',
    middlewares: [],
    globalMiddlewares: [],
    router: {
      admin: ['/saveWithSummary'],
    },
  } as ModuleConfig;
};
src/modules/shop/controller/admin/recycleappointment.ts
New file
@@ -0,0 +1,53 @@
import { Inject } from "@midwayjs/core";
import { CoolController, BaseController } from "@cool-midway/core";
import { RecycleAppointmentEntity } from "../../entity/recycleappointment";
import { RecycleAppointmentService } from "../../service/recycleappointment";
import { BaseSysDepartmentEntity } from "../../../base/entity/sys/department";
import { BasicdataSorterEntity } from "../../../basicdata/entity/sorter";
import { WithDeptFilterWhere } from '../../../base/middleware/with-dept-filter-where'
/**
 * 预约管理
 */
@CoolController({
    api: ["add", "delete", "update", "info", "list", "page"],
    entity: RecycleAppointmentEntity,
    service: RecycleAppointmentService,
    pageQueryOp: {
        keyWordLikeFields: ["a.appointmentNo", "a.contactName", "a.contactPhone"],
        fieldEq: ["a.status", "a.departmentId", "a.sorterId"],
        select: [
            "a.*",
            "b.name AS departmentName",
            "c.businessName AS sorterName"
        ],
        join: [
            {
                entity: BaseSysDepartmentEntity,
                alias: "b",
                condition: "a.departmentId = b.id",
                type: "leftJoin"
            },
            {
                entity: BasicdataSorterEntity,
                alias: "c",
                condition: "a.sorterId = c.businessId",
                type: "leftJoin"
            }
        ],
        where: async (ctx) => {
            const { startDate, endDate } = ctx.request.body;
            const conditions: any[][] = [];
            if (startDate && endDate) {
                conditions.push(["a.appointmentDate BETWEEN :startDate AND :endDate", { startDate, endDate }]);
            }
            conditions.push(...(await WithDeptFilterWhere(ctx, { alias: 'a', field: 'departmentId' })));
            return conditions;
        }
    }
})
export class AdminRecycleAppointmentController extends BaseController {
    @Inject()
    recycleAppointmentService: RecycleAppointmentService;
}
src/modules/shop/controller/admin/recycleappointmentlog.ts
New file
@@ -0,0 +1,25 @@
import { Inject } from '@midwayjs/core';
import { CoolController, BaseController } from '@cool-midway/core';
import { RecycleAppointmentLogEntity } from '../../entity/recycleappointmentlog';
import { RecycleAppointmentLogService } from '../../service/recycleappointmentlog';
/**
 * 回收物品预约信息详细日志控制器
 */
@CoolController({
  api: ['add', 'delete', 'update', 'info', 'list', 'page'],
  entity: RecycleAppointmentLogEntity,
  service: RecycleAppointmentLogService,
  pageQueryOp: {
    fieldEq: [
      'a.appointmentId',
      'a.action',
      'a.operatorType',
      'a.operatorId',
    ],
  },
})
export class AdminRecycleAppointmentLogController extends BaseController {
  @Inject()
  recycleAppointmentLogService: RecycleAppointmentLogService;
}
src/modules/shop/controller/admin/recycleitem.ts
New file
@@ -0,0 +1,41 @@
import { CoolController, BaseController } from '@cool-midway/core';
import { Inject } from '@midwayjs/core';
import { RecycleItemEntity } from '../../entity/recycleitem';
import { RecycleItemService } from '../../service/recycleitem';
import { BaseSysDepartmentEntity } from "../../../base/entity/sys/department";
import { WithDeptFilterWhere } from '../../../base/middleware/with-dept-filter-where'
/**
 * 回收物品
 */
@CoolController({
  api: ['add', 'delete', 'update', 'info', 'list', 'page'],
  entity: RecycleItemEntity,
  service: RecycleItemService,
  pageQueryOp: {
    keyWordLikeFields: ['a.name'],
    fieldEq: ['a.itemId', 'a.category', 'a.status'],
    select: [
      "a.*",
      "b.name AS departmentName"
    ],
    join: [
      {
        entity: BaseSysDepartmentEntity,
        alias: "b",
        condition: "a.departmentId = b.id",
        type: "leftJoin"
      }
    ],
    where: async (ctx) => {
      const conditions: any[][] = [];
      conditions.push(...(await WithDeptFilterWhere(ctx, { alias: 'a', field: 'departmentId' })));
      return conditions;
    }
  },
})
export class RecycleItemController extends BaseController {
  @Inject()
  recycleItemService: RecycleItemService;
}
src/modules/shop/controller/admin/recycleorder.ts
New file
@@ -0,0 +1,57 @@
import { Inject,Body,Post } from "@midwayjs/core";
import { CoolController, BaseController } from "@cool-midway/core";
import { RecycleOrderEntity } from "../../entity/recycleorder";
import { RecycleOrderService } from "../../service/recycleorder";
import { BaseSysDepartmentEntity } from '../../../base/entity/sys/department';
import { WithDeptFilterWhere } from '../../../base/middleware/with-dept-filter-where'
/**
 * 回收订单后台管理
 */
@CoolController({
    api: ["add", "delete", "update", "info", "list", "page"],
    entity: RecycleOrderEntity,
    service: RecycleOrderService,
    pageQueryOp: {
        keyWordLikeFields: ["a.orderNo"],
        fieldEq: ["a.orderType", "a.status"],
        select: ["a.*", "c.name AS departmentName"],
        join: [
            {
                entity: BaseSysDepartmentEntity,
                alias: "c",
                condition: 'a.departmentId = c.id',
                type: "leftJoin"
            }
        ],
        where: async (ctx) => {
            const { startTime, endTime } = ctx.request.body;
            const conditions: any[][] = [];
            if (startTime && endTime) {
                conditions.push(["a.finishTime BETWEEN :startTime AND :endTime", { startTime, endTime }]);
            }
            conditions.push(...(await WithDeptFilterWhere(ctx, { alias: 'a', field: 'departmentId' })));
            return conditions;
        },
        addOrderBy: {
            createTime: 'desc',
        },
    }
})
export class AdminRecycleOrderController extends BaseController {
    @Inject()
    recycleOrderService: RecycleOrderService;
    @Post("/saveWithSummary")
    async saveWithSummary(
        @Body()
        body: {
            orderNo: string;
            items: any[];
        }
    ) {
        await this.recycleOrderService.saveWithSummary(body.orderNo, body.items);
        return this.ok();
    }
}
src/modules/shop/controller/admin/recycleorderitem.ts
New file
@@ -0,0 +1,20 @@
import { CoolController, BaseController } from '@cool-midway/core';
import { RecycleOrderItemEntity } from '../../entity/recycleorderitem';
import { RecycleOrderItemService } from '../../service/recycleorderitem';
/**
 * 订单详情管理
 */
@CoolController({
  api: ['add', 'delete', 'update', 'info', 'list', 'page'],
  entity: RecycleOrderItemEntity,
  service: RecycleOrderItemService,
  pageQueryOp: {
    fieldEq: ['a.orderNo', 'a.itemId', 'a.categoryId'],
    fieldLike: ['a.itemName'],
  },
  listQueryOp: {
    fieldEq: ['a.orderNo', 'a.itemId', 'a.categoryId'],
  },
})
export class AdminRecycleOrderItemController extends BaseController {}
src/modules/shop/controller/admin/transaction.ts
New file
@@ -0,0 +1,53 @@
import { CoolController, BaseController } from '@cool-midway/core';
import { RecycleTransactionEntity } from '../../entity/transaction';
import { RecycleTransactionService } from '../../service/transaction';
import { UserInfoEntity } from '../../../user/entity/info';
import { BaseSysDepartmentEntity } from '../../../base/entity/sys/department';
import { WithDeptFilterWhere } from '../../../base/middleware/with-dept-filter-where'
/**
 * 流水记录管理
 */
@CoolController({
    api: ['add', 'delete', 'update', 'info', 'list', 'page'],
    entity: RecycleTransactionEntity,
    service: RecycleTransactionService,
    pageQueryOp: {
        select: ['a.*', 'b.userName as userName', 'c.name as departmentName'],
        keyWordLikeFields: ['a.remark'],
        fieldEq: [
            { column: 'a.userId', requestParam: 'userId' },
            { column: 'a.orderId', requestParam: 'orderId' },
            { column: 'a.departmentId', requestParam: 'departmentId' },
            { column: 'a.type', requestParam: 'type' },
            { column: 'a.sourceType', requestParam: 'sourceType' }
        ],
        join: [
            {
                entity: UserInfoEntity,
                alias: 'b',
                condition: 'a.userId = b.unionid',
                type: 'leftJoin'
            },
            {
                entity: BaseSysDepartmentEntity,
                alias: 'c',
                condition: 'a.departmentId = c.id',
                type: 'leftJoin'
            }
        ],
        where: async (ctx) => {
            const { startTime, endTime } = ctx.request.body;
            const conditions: any[][] = [];
            if (startTime && endTime) {
                conditions.push(['a.createTime BETWEEN :startTime AND :endTime', { startTime, endTime }]);
            }
            conditions.push(...(await WithDeptFilterWhere(ctx, { alias: 'a', field: 'departmentId' })));
            return conditions;
        },
        addOrderBy: {
            createTime: 'desc'
        }
    }
})
export class AdminFinanceTransactionController extends BaseController { }
src/modules/shop/controller/app/recycleorder.ts
New file
@@ -0,0 +1,25 @@
import { Inject } from "@midwayjs/core";
import { CoolController, BaseController } from "@cool-midway/core";
import { RecycleOrderEntity } from "../../entity/recycleorder";
import { RecycleOrderService } from "../../service/recycleorder";
/**
 * 回收订单App客户端
 */
@CoolController({
  api: ["add", "info", "page"],
  entity: RecycleOrderEntity,
  service: RecycleOrderService,
  pageQueryOp: {
    fieldEq: ["a.status"],
    where: async (ctx) => {
      return [
        ["a.campusUserId = :userId", { userId: ctx.user.id }]
      ];
    }
  }
})
export class AppRecycleOrderController extends BaseController {
  @Inject()
  recycleOrderService: RecycleOrderService;
}
src/modules/shop/controller/app/recycleorderitem.ts
New file
@@ -0,0 +1,29 @@
import { Inject } from "@midwayjs/core";
import { CoolController, BaseController } from "@cool-midway/core";
import { RecycleOrderItemEntity } from "../../entity/recycleorderitem";
import { RecycleOrderItemService } from "../../service/recycleorderitem";
import { RecycleItemEntity } from "../../entity/recycleitem";
/**
 * 回收订单条目-应用端
 */
@CoolController({
  api: ["info", "page"],
  entity: RecycleOrderItemEntity,
  service: RecycleOrderItemService,
  pageQueryOp: {
    select: ["a.*", "b.mainPic"],
    fieldEq: ["a.orderId"],
    join: [
      {
        entity: RecycleItemEntity,
        alias: "b",
        condition: "a.itemId = b.id",
      },
    ],
  },
})
export class AppRecycleOrderitemController extends BaseController {
  @Inject()
  recycleOrderItemService: RecycleOrderItemService;
}
src/modules/shop/entity/recycleappointment.ts
New file
@@ -0,0 +1,66 @@
import { StringLiteralType } from 'typescript';
import { BaseEntity } from '../../base/entity/base';
import { Column, Entity, Index } from "typeorm";
/**
 * 回收物品预约信息
 */
@Entity("t_recycle_appointment")
export class RecycleAppointmentEntity extends BaseEntity {
    @Index({ unique: true })
    @Column({ comment: "预约编号" })
    appointmentNo: string;
    @Index()
    @Column({ comment: '学校ID', nullable: true })   // 使用归属部门,这里建议选顶层学校
    departmentId: number;
    departmentName: string;
    @Index()
    @Column({ comment: "用户ID" })
    campusUserId: string;
    campusUserName: string;
    @Column({ comment: "联系人" })
    contactName: string;
    @Column({ comment: "联系电话" })
    contactPhone: string;
    @Column({ comment: "详细地址" })
    address: string;
    @Column({ comment: "经度", nullable: true })
    longitude: number;
    @Column({ comment: "纬度",nullable: true })
    latitude: number;
    @Column({ comment:"预约日期"})
    appointmentDate:string;
     @Column({comment:"预约时间段", default:"傍晚 18:00-20:00"})
    appointmentSlot:string;
    @Column({ comment:"预计重量",default: 0 })
    expectedWeight:number;
    @Column({ comment: "状态", dict: ['待审核', '待接单', '已接单', '上门中', '已完成','已取消', '未知'], default: '未知' })
    status: string;
    @Index()
    @Column({ comment: "分拣员ID", nullable: true, default: 0 })
    sorterId: string;
    sorterName: string;
    @Column({ comment:"回收订单ID", nullable:true})
    recycleOrderId:string;
}
src/modules/shop/entity/recycleappointmentlog.ts
New file
@@ -0,0 +1,39 @@
import { BaseEntity } from '../../base/entity/base';
import { Column, Entity, Index } from "typeorm";
/**
 * 回收物品预约信息-详细log
 */
@Entity("t_recycle_appointment_log")
export class RecycleAppointmentLogEntity extends BaseEntity {
    @Index()
    @Column({ comment: "预约ID" })
    appointmentId: number;
    @Column({
        comment: "操作类型",
        dict: [
            "创建预约",
            "审核通过",
            "分配人员",
            "接单",
            "到达",
            "开始回收",
            "完成",
            "取消",
            "改期"
        ]
    })
    action: string;
    @Column({ comment: "操作人类型" })   // 用户/分拣员/管理员
    operatorType: string;
    @Column({ comment: "操作人ID" })
    operatorId: number;
    @Column({ comment: "描述" })
    remark: string;
}
src/modules/shop/entity/recycleitem.ts
New file
@@ -0,0 +1,48 @@
import { BaseEntity } from '../../base/entity/base';
import { Column, Entity, Index } from 'typeorm';
/**
 * 回收物品信息
 */
@Entity('t_recycle_item')
export class RecycleItemEntity extends BaseEntity {
  @Index()
  @Column({ comment: '所属单位' })
  departmentId: string;
  departmentName: string;
  @Column({ comment: '分类ID' })
  itemId: string;
  @Index()
  @Column({ comment: '名称' })
  itemName: string;
  @Index()
  @Column({ comment: '包含品类' })
  including: string;
  @Column({ comment: '单位' })
  unit: string;
  @Column({
    comment: '价格',
    type: 'decimal',
    precision: 10,
    scale: 2,
  })
  price: number;
  @Column({ comment: '碳积分' })
  carbonPoint: number;
  @Index()
  @Column({ comment: '废品类别编码' })
  category: string;
  @Column({ comment: '状态', dict: ['启用', '禁用'], default: 0 })
  status: number;
}
src/modules/shop/entity/recycleorder.ts
New file
@@ -0,0 +1,56 @@
import { BaseEntity } from '../../base/entity/base';
import { Column, Entity, Index } from "typeorm";
/**
 * 回收物品订单
 */
@Entity("t_recycle_order")
export class RecycleOrderEntity extends BaseEntity {
    @Index({ unique: true })
    @Column({ comment: "订单编号" })
    orderNo: string;
    @Column({ comment: "订单类型", dict: ['现场', '预约', '大屏', '活动'], default: '现场' })
    orderType: string;
    @Index()
    @Column({ comment: "用户ID" })
    campusUserId: string;
    campusUserName: string;
    @Index()
    @Column({ comment: '学校ID', nullable: true })   // 使用归属部门,这里建议选顶层学校
    departmentId: number;
    departmentName: string;
    @Index()
    @Column({ comment: "站点ID", nullable: true})
    siteId: string;
    @Index()
    @Column({ comment: "分拣员ID", nullable: true, default: 0 })
    sorterId: string;
    @Index()
    @Column({ comment: "预约ID", nullable: true})
    appointmentId: number;
    @Column({ comment: '状态', dict: ['待处理', '进行中', '已完成', '已取消'], default: '已完成', })
    status: string;
    @Column({ comment: '总重量', type: 'decimal', precision: 10, scale: 2, default: 0, })
    totalWeight: number;
    @Column({ comment: '总金额', type: 'decimal', precision: 10, scale: 2, default: 0, })
    totalAmount: number;
    @Column({ comment: '总碳积分', type: 'decimal', precision: 10, scale: 2, default: 0, })
    totalCarbonPoint: number;
    @Column({ comment: '完成时间', nullable: true })
    finishTime: Date;
}
src/modules/shop/entity/recycleorderitem.ts
New file
@@ -0,0 +1,34 @@
import { BaseEntity } from '../../base/entity/base';
import { Column, Entity, Index } from 'typeorm';
/**
 * 垃圾回收订单详情信息
 */
@Entity('t_recycle_order_item')
export class RecycleOrderItemEntity extends BaseEntity {
    @Index()
    @Column({ comment: '订单NO' })
    orderNo: string;
    @Index()
    @Column({ comment: '垃圾分类ID' })
    itemId: string;
    @Column({ comment: '垃圾分类名称' })
    itemName: string;
    @Column({ comment: '固废国标分类' })
    category: string;
    @Column({ comment: '重量', type: 'decimal', precision: 10, scale: 2, default: 0, })
    weight: number;
    @Column({ comment: '单价', type: 'decimal', precision: 10, scale: 2, default: 0, })
    unitPrice: number;
    @Column({ comment: '金额', type: 'decimal', precision: 10, scale: 2, default: 0, })
    amount: number;
    @Column({ comment: '碳积分', type: 'decimal', precision: 10, scale: 2, default: 0, })
    carbonPoint: number;
}
src/modules/shop/entity/transaction.ts
New file
@@ -0,0 +1,42 @@
import { BaseEntity } from '../../base/entity/base';
import { Column, Entity, Index } from 'typeorm';
/**
 * 流水记录
 */
@Entity('t_recycle_transaction')
export class RecycleTransactionEntity extends BaseEntity {
  @Index()
  @Column({ comment: '用户ID' })
  userId: string;
  @Index()
  @Column({ comment: '机构ID', nullable: true })
  departmentId: number;
  @Column({ comment: '流水类型', dict: ['收入', '支出'], default: '收入' })
  type: string;
  @Column({
    comment: '金额',
    type: 'decimal',
    precision: 15,
    scale: 2,
  })
  amount: number;
  @Column({
    comment: '来源类型',
    dict: ['回收', '商城', '赠送', '接受', '捐赠', '签到'],
    default: '回收',
  })
  sourceType: string;
  @Column({ comment: '备注', nullable: true })
  remark: string;
  @Index()
  @Column({ comment: '订单ID', nullable: true })
  orderId: string;
}
src/modules/shop/service/recycleappointment.ts
New file
@@ -0,0 +1,14 @@
import { Provide } from "@midwayjs/core";
import { BaseService } from "@cool-midway/core";
import { InjectEntityModel } from "@midwayjs/typeorm";
import { Repository } from "typeorm";
import { RecycleAppointmentEntity } from "../entity/recycleappointment";
/**
 * 回收物品订单-预约服务
 */
@Provide()
export class RecycleAppointmentService extends BaseService {
  @InjectEntityModel(RecycleAppointmentEntity)
  recycleAppointmentEntity: Repository<RecycleAppointmentEntity>;
}
src/modules/shop/service/recycleappointmentlog.ts
New file
@@ -0,0 +1,14 @@
import { Provide } from "@midwayjs/core";
import { BaseService } from "@cool-midway/core";
import { InjectEntityModel } from "@midwayjs/typeorm";
import { Repository } from "typeorm";
import { RecycleAppointmentLogEntity } from "../entity/recycleappointmentlog";
/**
 * 回收物品订单-预约的变动
 */
@Provide()
export class RecycleAppointmentLogService extends BaseService {
  @InjectEntityModel(RecycleAppointmentLogEntity)
  recycleAppointmentLogEntity: Repository<RecycleAppointmentLogEntity>;
}
src/modules/shop/service/recycleitem.ts
New file
@@ -0,0 +1,15 @@
import { Provide, Inject } from '@midwayjs/core';
import { BaseService, CoolTransaction } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository, QueryRunner } from 'typeorm';
import { RecycleItemEntity } from '../entity/recycleitem';
/**
 * 碳积分流转记录
 */
@Provide()
export class RecycleItemService extends BaseService {
  @InjectEntityModel(RecycleItemEntity)
  recycleItemEntity: Repository<RecycleItemEntity>;
}
src/modules/shop/service/recycleorder.ts
New file
@@ -0,0 +1,143 @@
import { Provide } from "@midwayjs/core";
import { BaseService, CoolTransaction } from "@cool-midway/core";
import { InjectEntityModel } from "@midwayjs/typeorm";
import { Repository, QueryRunner, EntityManager } from "typeorm";
import { RecycleItemEntity } from "../entity/recycleitem";
import { RecycleOrderEntity } from "../entity/recycleorder";
import { RecycleOrderItemEntity } from '../entity/recycleorderitem';
import { UserInfoEntity } from '../../user/entity/info';
import { RecycleTransactionEntity } from '../entity/transaction';
/**
 * 回收物品订单服务
 */
@Provide()
export class RecycleOrderService extends BaseService {
  @InjectEntityModel(RecycleOrderEntity)
  recycleOrderEntity: Repository<RecycleOrderEntity>;
  @InjectEntityModel(RecycleItemEntity)
  recycleItemEntity: Repository<RecycleItemEntity>;
  @InjectEntityModel(RecycleOrderItemEntity)
  recycleOrderItemEntity: Repository<RecycleOrderItemEntity>;
  @InjectEntityModel(RecycleTransactionEntity)
  recycleTransactionEntity: Repository<RecycleTransactionEntity>;
  /**
   * 重写新增,处理订单及详情
   */
  @CoolTransaction()
  async add(param: any, queryRunner?: QueryRunner) {
    const { items, ...orderData } = param;
    // 保存订单
    const order = await queryRunner?.manager.save(RecycleOrderEntity, orderData);
    // 保存详情
    if (items && items.length > 0) {
      const itemEntities = items.map(e => {
        return {
          ...e,
          orderId: order.id,
        };
      });
      await queryRunner?.manager.save(RecycleItemEntity, itemEntities);
    }
    return order;
  }
  /**
   * 重写更新
   */
  @CoolTransaction()
  async update(param: any, queryRunner?: QueryRunner) {
    const { items, id, ...orderData } = param;
    await queryRunner?.manager.update(RecycleOrderEntity, id, orderData);
    if (items) {
      // 先删除旧详情再插入新详情(简单处理)
      await queryRunner?.manager.delete(RecycleItemEntity, { orderId: id });
      const itemEntities = items.map(e => {
        return {
          ...e,
          orderId: id,
        };
      });
      await queryRunner?.manager.save(RecycleItemEntity, itemEntities);
    }
  }
  /**
 * 重写删除逻辑,同步删除订单项
 */
  @CoolTransaction()
  async delete(ids: number[], queryRunner?: QueryRunner) {
    for (const id of ids) {
      const order = await this.recycleOrderEntity.findOneBy({ id });
      if (order) {
        await queryRunner?.manager.delete(RecycleOrderItemEntity, {
          orderNo: order.orderNo,
        });
      }
    }
    await queryRunner?.manager.delete(RecycleOrderEntity, ids);
  }
  @CoolTransaction()
  async saveWithSummary(orderNo: string, items: any[]) {
    // 1️⃣ 先删掉旧明细
    await RecycleOrderItemEntity.delete({ orderNo });
    // 2️⃣ 重新插入
    const newItems = items.map(i => ({
      ...i,
      orderNo
    }));
    console.log(newItems)
    await RecycleOrderItemEntity.save(newItems);
    // 3️⃣ 汇总
    const all = await RecycleOrderItemEntity.find({
      where: { orderNo }
    });
    const totalWeight = all.reduce((s, i) => s + Number(i.weight), 0);
    const totalCarbonPoint = all.reduce((s, i) => s + Number(i.carbonPoint), 0);
    const totalAmount = all.reduce((s, i) => s + Number(i.amount), 0);
    await RecycleOrderEntity.update(
      { orderNo },
      { totalWeight, totalCarbonPoint, totalAmount }
    );
    // 5️⃣ 更新用户碳余额
    const order = await RecycleOrderEntity.findOne({
      where: { orderNo },
      select: ['campusUserId']
    });
    if (order?.campusUserId) {
      await UserInfoEntity.update(
        { unionid: order.campusUserId },
        {
          carbonBalance: () => `carbonBalance + ${totalCarbonPoint}`
        }
      );
    }
    // 7️⃣ ✅ 用 add() 写流水(重点)
    await RecycleTransactionEntity.save({
      userId: order?.campusUserId,
      departmentId: order?.departmentId,
      type: '收入',
      amount: totalCarbonPoint,
      sourceType: '回收',
      remark: `回收订单 ${orderNo} 获得碳积分`,
      orderId: orderNo
    });
  };
}
src/modules/shop/service/recycleorderitem.ts
New file
@@ -0,0 +1,14 @@
import { Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { RecycleOrderItemEntity } from '../entity/recycleorderitem';
/**
 * 订单详情服务
 */
@Provide()
export class RecycleOrderItemService extends BaseService {
  @InjectEntityModel(RecycleOrderItemEntity)
  recycleOrderItemEntity: Repository<RecycleOrderItemEntity>;
}
src/modules/shop/service/transaction.ts
New file
@@ -0,0 +1,15 @@
import { Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { RecycleTransactionEntity } from '../entity/transaction';
/**
 * 碳积分流转记录
 */
@Provide()
export class RecycleTransactionService extends BaseService {
  @InjectEntityModel(RecycleTransactionEntity)
  recycleTransactionEntity: Repository<RecycleTransactionEntity>;
}
src/modules/user/controller/admin/info.ts
@@ -1,5 +1,7 @@
import { CoolController, BaseController } from '@cool-midway/core';
import { UserInfoEntity } from '../../entity/info';
import { BaseSysDepartmentEntity } from '../../../base/entity/sys/department';
import { WithDeptFilterWhere } from '../../../base/middleware/with-dept-filter-where'
/**
 * 用户信息
@@ -10,6 +12,21 @@
  pageQueryOp: {
    fieldEq: ['a.status', 'a.gender', 'a.loginType'],
    keyWordLikeFields: ['a.nickName', 'a.phone'],
    select: ["a.*", "c.name AS departmentName"],
    join: [
      {
        entity: BaseSysDepartmentEntity,
        alias: "c",
        condition: 'a.departmentId = c.id',
        type: "leftJoin"
      }
    ],
    where: async (ctx) => {
      const conditions: any[][] = [];
      conditions.push(...(await WithDeptFilterWhere(ctx, { alias: 'a', field: 'departmentId' })));
      return conditions;
    }
  },
})
export class AdminUserInfoController extends BaseController {}
export class AdminUserInfoController extends BaseController { }
src/modules/user/entity/info.ts
@@ -1,10 +1,10 @@
import { BaseEntity } from '../../base/entity/base';
import { Column, Entity, Index } from 'typeorm';
import { Column, Entity, Index,BeforeInsert } from 'typeorm';
/**
 * 用户信息
 */
@Entity('user_info')
@Entity('t_school_user_info')
export class UserInfoEntity extends BaseEntity {
  @Index({ unique: true })
  @Column({ comment: '登录唯一ID', nullable: true })
@@ -20,8 +20,46 @@
  @Column({ comment: '手机号', nullable: true })
  phone: string;
  @Column({ comment: '生日', type: 'date', nullable: true })
  birthday: Date;
  @Column({ comment: '性别', dict: ['未知', '男', '女'], default: 0 })
  gender: number;
  @Index()
  @Column({ comment: '角色', dict: ['老师', '学生', '其他'], default: '学生' })
  role: string;
  @Index()
  @Column({ comment: '学校ID' })   // 使用归属部门,这里建议选顶层学校
  departmentId: number;
  departmentName: string;
  @Index()
  @Column({ comment: '碳积分', default: 0 })
  carbonBalance: number;
  @Index()
  @Column({ comment: '学院ID', nullable: true })
  collegeId: number;
  @Column({ comment: '学院名称', nullable: true })
  collegeName: string;
  @Index()
  @Column({ comment: '专业ID', nullable: true })
  majorId: number;
  @Column({ comment: '专业名称', nullable: true })
  majorName: string;
  @Index()
  @Column({ comment: '班级ID', nullable: true })
  classId: number;
  @Column({ comment: '班级名称', nullable: true })
  className: string;
  @Column({ comment: '状态', dict: ['禁用', '正常', '已注销'], default: 1 })
  status: number;
@@ -34,4 +72,5 @@
  @Column({ comment: '介绍', type: 'text', nullable: true })
  description: string;
}
src/modules/user/service/info.ts
@@ -46,7 +46,7 @@
   */
  async person(id) {
    const info = await this.userInfoEntity.findOneBy({ id: Equal(id) });
    delete info.password;
    //delete info.password;
    return info;
  }
@@ -59,10 +59,10 @@
      { id: userId },
      {
        status: 2,
        phone: null,
        unionid: null,
        phone: "",
        unionid: "",
        nickName: `已注销-00${userId}`,
        avatarUrl: null,
        avatarUrl: "",
      }
    );
  }
@@ -85,7 +85,7 @@
          uuid() + '.png'
        );
      }
    } catch (err) {}
    } catch (err) { }
    try {
      return await this.userInfoEntity.update({ id }, param);
    } catch (err) {
@@ -99,13 +99,21 @@
   * @param password
   * @param 验证码
   */
  async updatePassword(userId, password, code) {
    const user = await this.userInfoEntity.findOneBy({ id: userId });
  async updatePassword(userId: string, password: string, code: string) {
    const user = await this.userInfoEntity.findOneBy({ unionid: userId });
    if (!user) {
      throw new CoolCommException('用户不存在');
    }
    const check = await this.userSmsService.checkCode(user.phone, code);
    if (!check) {
      throw new CoolCommException('验证码错误');
    }
    await this.userInfoEntity.update(user.id, { password: md5(password) });
    await this.userInfoEntity.update(user.unionid, {
      password: md5(password),
    });
  }
  /**
uploads/capture/20260810/GT7568358_1786354561815.jpg
Binary files differ
uploads/capture/20260810/GT7568358_1786354599954.jpg
Binary files differ
uploads/capture/20260810/GU7398831_1786354575450.jpg
Binary files differ
uploads/capture/20260810/GV6662505_1786354596817.jpg
Binary files differ
uploads/capture/20260810/GV6662514_1786354589019.jpg
Binary files differ
uploads/capture/20260810/GV7474259_1786354585367.jpg
Binary files differ
uploads/capture/20260810/GV7474275_1786354579899.jpg
Binary files differ
uploads/capture/20260810/GV7474291_1786354592342.jpg
Binary files differ