import { Inject, Provide } from '@midwayjs/core';
|
import { BaseService, CoolCommException, CoolTransaction } from '@cool-midway/core';
|
import { InjectEntityModel } from '@midwayjs/typeorm';
|
import { QueryRunner, Repository } from 'typeorm';
|
import * as moment from 'moment';
|
import { UserInfoEntity } from '../../user/entity/info';
|
import { BasicdataSorterEntity } from '../../basicdata/entity/sorter';
|
import { RecycleOrderEntity } from '../entity/recycleorder';
|
import { RecycleOrderItemEntity } from '../entity/recycleorderitem';
|
import { RecycleItemEntity } from '../entity/recycleitem';
|
import { RecycleTransactionEntity } from '../entity/transaction';
|
import { RecycleAppointmentEntity } from '../entity/recycleappointment';
|
import { RecycleAppointmentLogEntity } from '../entity/recycleappointmentlog';
|
import { BaseSysParamService } from '../../base/service/sys/param';
|
|
/**
|
* 分拣员 App 业务
|
*/
|
@Provide()
|
export class ShopSorterService extends BaseService {
|
@InjectEntityModel(UserInfoEntity)
|
userInfoEntity: Repository<UserInfoEntity>;
|
|
@InjectEntityModel(BasicdataSorterEntity)
|
basicdataSorterEntity: Repository<BasicdataSorterEntity>;
|
|
@InjectEntityModel(RecycleOrderEntity)
|
recycleOrderEntity: Repository<RecycleOrderEntity>;
|
|
@InjectEntityModel(RecycleOrderItemEntity)
|
recycleOrderItemEntity: Repository<RecycleOrderItemEntity>;
|
|
@InjectEntityModel(RecycleItemEntity)
|
recycleItemEntity: Repository<RecycleItemEntity>;
|
|
@InjectEntityModel(RecycleTransactionEntity)
|
recycleTransactionEntity: Repository<RecycleTransactionEntity>;
|
|
@InjectEntityModel(RecycleAppointmentEntity)
|
recycleAppointmentEntity: Repository<RecycleAppointmentEntity>;
|
|
@InjectEntityModel(RecycleAppointmentLogEntity)
|
recycleAppointmentLogEntity: Repository<RecycleAppointmentLogEntity>;
|
|
@Inject()
|
baseSysParamService: BaseSysParamService;
|
|
/**
|
* 读取打卡参数
|
*/
|
async getHitupConfig() {
|
const scoreRaw = await this.baseSysParamService.dataByKey('hitup.score');
|
const maxRaw = await this.baseSysParamService.dataByKey(
|
'hitup.maxmium.perday'
|
);
|
const score = Number(scoreRaw);
|
const maxPerDay = Number(maxRaw);
|
return {
|
score: Number.isFinite(score) && score >= 0 ? score : 1,
|
maxPerDay: Number.isFinite(maxPerDay) && maxPerDay >= 0 ? maxPerDay : 1,
|
};
|
}
|
|
/**
|
* 校验分拣员
|
*/
|
async getSorter(sorterId: string) {
|
const id = String(sorterId || '').trim();
|
if (!id) {
|
throw new CoolCommException('缺少分拣员信息');
|
}
|
const sorter = await this.basicdataSorterEntity.findOneBy({
|
businessId: id,
|
});
|
if (!sorter || sorter.status !== 0) {
|
throw new CoolCommException('分拣员账号不可用');
|
}
|
return sorter;
|
}
|
|
/**
|
* 校验回收码对应的平台用户(回收码即为 unionid)
|
*/
|
async checkUser(unionid: string) {
|
const uid = String(unionid || '').trim();
|
if (!uid) {
|
throw new CoolCommException('请扫描有效的回收码');
|
}
|
const user = await this.userInfoEntity.findOneBy({ unionid: uid });
|
if (!user || user.status !== 1) {
|
throw new CoolCommException('该用户不是平台用户');
|
}
|
return {
|
unionid: user.unionid,
|
nickName: user.nickName,
|
phone: user.phone,
|
role: user.role,
|
departmentId: user.departmentId,
|
carbonBalance: user.carbonBalance,
|
collegeName: user.collegeName,
|
className: user.className,
|
avatarUrl: user.avatarUrl,
|
};
|
}
|
|
/**
|
* 打卡积分
|
*/
|
@CoolTransaction()
|
async checkIn(
|
param: { unionid: string; sorterId: string },
|
queryRunner?: QueryRunner
|
) {
|
if (!queryRunner) {
|
throw new CoolCommException('事务启动失败');
|
}
|
const manager = queryRunner.manager;
|
const unionid = String(param.unionid || '').trim();
|
const sorter = await this.getSorter(param.sorterId);
|
const user = await manager.findOne(UserInfoEntity, {
|
where: { unionid },
|
});
|
if (!user || user.status !== 1) {
|
throw new CoolCommException('该用户不是平台用户');
|
}
|
|
const { score, maxPerDay } = await this.getHitupConfig();
|
const today = moment().format('YYYY-MM-DD');
|
const todayCount = await manager
|
.createQueryBuilder(RecycleOrderEntity, 'a')
|
.where('a.campusUserId = :unionid', { unionid })
|
.andWhere('a.orderType = :orderType', { orderType: '打卡' })
|
.andWhere('a.createTime >= :start AND a.createTime <= :end', {
|
start: `${today} 00:00:00`,
|
end: `${today} 23:59:59`,
|
})
|
.getCount();
|
|
if (todayCount >= maxPerDay) {
|
throw new CoolCommException('该用户今日打卡次数已达上限');
|
}
|
|
const orderNo = this.genOrderNo('CI');
|
const now = moment().format('YYYY-MM-DD HH:mm:ss');
|
const order = await manager.save(RecycleOrderEntity, {
|
orderNo,
|
orderType: '打卡',
|
campusUserId: unionid,
|
departmentId: user.departmentId,
|
siteId: sorter.workSiteId,
|
sorterId: sorter.businessId,
|
status: '已完成',
|
totalWeight: 0,
|
totalAmount: 0,
|
totalCarbonPoint: score,
|
finishTime: now as any,
|
});
|
|
if (score > 0) {
|
await manager
|
.createQueryBuilder()
|
.update(UserInfoEntity)
|
.set({
|
carbonBalance: () => `carbonBalance + ${score}`,
|
})
|
.where('unionid = :unionid', { unionid })
|
.execute();
|
|
await manager.save(RecycleTransactionEntity, {
|
userId: unionid,
|
departmentId: user.departmentId,
|
type: '收入',
|
amount: score,
|
sourceType: '签到',
|
remark: `打卡获得碳积分`,
|
orderId: orderNo,
|
});
|
}
|
|
return {
|
orderNo,
|
orderId: order.id,
|
score,
|
nickName: user.nickName,
|
unionid,
|
};
|
}
|
|
/**
|
* 回收品类列表(全部启用品类,按类别分组展示)
|
*/
|
async itemList(_sorterId?: string) {
|
const list = await this.recycleItemEntity.find({
|
where: { status: 0 },
|
order: { id: 'ASC' },
|
});
|
if (list.length) {
|
return list;
|
}
|
return this.recycleItemEntity.find({
|
order: { id: 'ASC' },
|
});
|
}
|
|
/**
|
* 保存现场/预约回收清单
|
*/
|
@CoolTransaction()
|
async saveOrder(
|
param: {
|
sorterId: string;
|
unionid: string;
|
appointmentId?: number;
|
items: any[];
|
},
|
queryRunner?: QueryRunner
|
) {
|
if (!queryRunner) {
|
throw new CoolCommException('事务启动失败');
|
}
|
const manager = queryRunner.manager;
|
const unionid = String(param.unionid || '').trim();
|
const sorter = await this.getSorter(param.sorterId);
|
const user = await manager.findOne(UserInfoEntity, {
|
where: { unionid },
|
});
|
if (!user || user.status !== 1) {
|
throw new CoolCommException('该用户不是平台用户');
|
}
|
|
const items = Array.isArray(param.items) ? param.items : [];
|
if (!items.length) {
|
throw new CoolCommException('请添加回收清单');
|
}
|
|
let appointment: RecycleAppointmentEntity = null;
|
if (param.appointmentId) {
|
appointment = await manager.findOne(RecycleAppointmentEntity, {
|
where: { id: Number(param.appointmentId) },
|
});
|
if (!appointment) {
|
throw new CoolCommException('预约单不存在');
|
}
|
if (appointment.status === '已完成') {
|
throw new CoolCommException('该预约已完成');
|
}
|
if (appointment.status === '已取消') {
|
throw new CoolCommException('该预约已取消');
|
}
|
if (appointment.campusUserId && appointment.campusUserId !== unionid) {
|
throw new CoolCommException('回收码与预约单用户不一致');
|
}
|
}
|
|
const itemRows = [];
|
let totalWeight = 0;
|
let totalAmount = 0;
|
let totalCarbonPoint = 0;
|
|
for (const row of items) {
|
const catalog = await manager.findOne(RecycleItemEntity, {
|
where: { itemId: String(row.itemId) },
|
});
|
if (!catalog) {
|
throw new CoolCommException(`回收品类不存在:${row.itemId || ''}`);
|
}
|
const weight = Number(row.weight);
|
if (!weight || weight <= 0) {
|
throw new CoolCommException(`请填写${catalog.itemName}的计量`);
|
}
|
const unitPrice = Number(catalog.price || 0);
|
const unitCarbon = Number(catalog.carbonPoint || 0);
|
const amount = Number((weight * unitPrice).toFixed(2));
|
const carbonPoint = Number((weight * unitCarbon).toFixed(2));
|
itemRows.push({
|
itemId: catalog.itemId,
|
itemName: catalog.itemName,
|
category: catalog.category,
|
weight,
|
unitPrice,
|
amount,
|
carbonPoint,
|
});
|
totalWeight += weight;
|
totalAmount += amount;
|
totalCarbonPoint += carbonPoint;
|
}
|
|
totalWeight = Number(totalWeight.toFixed(2));
|
totalAmount = Number(totalAmount.toFixed(2));
|
totalCarbonPoint = Number(totalCarbonPoint.toFixed(2));
|
|
const orderNo = this.genOrderNo(appointment ? 'YY' : 'XC');
|
const now = moment().format('YYYY-MM-DD HH:mm:ss');
|
const order = await manager.save(RecycleOrderEntity, {
|
orderNo,
|
orderType: appointment ? '预约' : '现场',
|
campusUserId: unionid,
|
departmentId: user.departmentId || sorter.departmentId,
|
siteId: sorter.workSiteId,
|
sorterId: sorter.businessId,
|
appointmentId: appointment ? appointment.id : null,
|
status: '已完成',
|
totalWeight,
|
totalAmount,
|
totalCarbonPoint,
|
finishTime: now as any,
|
});
|
|
await manager.save(
|
RecycleOrderItemEntity,
|
itemRows.map(item => ({
|
...item,
|
orderNo,
|
}))
|
);
|
|
if (totalCarbonPoint > 0) {
|
await manager
|
.createQueryBuilder()
|
.update(UserInfoEntity)
|
.set({
|
carbonBalance: () => `carbonBalance + ${totalCarbonPoint}`,
|
})
|
.where('unionid = :unionid', { unionid })
|
.execute();
|
|
await manager.save(RecycleTransactionEntity, {
|
userId: unionid,
|
departmentId: user.departmentId,
|
type: '收入',
|
amount: totalCarbonPoint,
|
sourceType: '回收',
|
remark: `回收订单 ${orderNo} 获得碳积分`,
|
orderId: orderNo,
|
});
|
}
|
|
if (appointment) {
|
await manager.update(RecycleAppointmentEntity, appointment.id, {
|
status: '已完成',
|
sorterId: sorter.businessId,
|
recycleOrderId: orderNo,
|
});
|
await manager.save(RecycleAppointmentLogEntity, {
|
appointmentId: appointment.id,
|
action: '完成',
|
operatorType: '分拣员',
|
operatorId: sorter.id,
|
remark: `上门回收完成,订单 ${orderNo}`,
|
});
|
}
|
|
return {
|
orderNo,
|
orderId: order.id,
|
totalWeight,
|
totalAmount,
|
totalCarbonPoint,
|
};
|
}
|
|
/**
|
* 预约列表
|
*/
|
async appointmentPage(query: any) {
|
const { sorterId, status, page = 1, size = 10, keyWord, pool } = query || {};
|
const sorter = await this.getSorter(sorterId);
|
const find = this.recycleAppointmentEntity
|
.createQueryBuilder('a')
|
.select([
|
'a.*',
|
'b.nickName AS campusUserName',
|
'c.businessName AS sorterName',
|
])
|
.leftJoin(UserInfoEntity, 'b', 'a.campusUserId = b.unionid')
|
.leftJoin(BasicdataSorterEntity, 'c', 'a.sorterId = c.businessId');
|
|
if (sorter.departmentId) {
|
find.andWhere(
|
'(CAST(a.departmentId AS CHAR) = :deptId OR a.departmentId IS NULL)',
|
{ deptId: String(sorter.departmentId) }
|
);
|
}
|
if (pool === 'new') {
|
find.andWhere('a.status IN (:...newSt)', {
|
newSt: ['新增', '待审核', '待接单', '未知'],
|
});
|
find.andWhere(
|
"(a.sorterId IS NULL OR CAST(a.sorterId AS CHAR) = '' OR CAST(a.sorterId AS CHAR) = '0')"
|
);
|
} else if (pool === 'mine') {
|
find.andWhere('a.sorterId = :sid', { sid: sorter.businessId });
|
find.andWhere('a.status NOT IN (:...done)', {
|
done: ['已完成', '已取消'],
|
});
|
} else if (status) {
|
find.andWhere('a.status = :status', { status });
|
}
|
if (keyWord) {
|
find.andWhere(
|
'(a.appointmentNo LIKE :kw OR a.contactName LIKE :kw OR a.contactPhone LIKE :kw OR a.address LIKE :kw)',
|
{ kw: `%${keyWord}%` }
|
);
|
}
|
|
find.orderBy('a.appointmentDate', 'DESC');
|
find.addOrderBy('a.id', 'DESC');
|
return this.entityRenderPage(find, { ...query, page, size }, false);
|
}
|
|
/**
|
* 领取预约单到分拣员名下
|
*/
|
async claimAppointment(param: { id: number; sorterId: string }) {
|
const sorter = await this.getSorter(param.sorterId);
|
const appointment = await this.recycleAppointmentEntity.findOneBy({
|
id: Number(param.id),
|
});
|
if (!appointment) {
|
throw new CoolCommException('预约单不存在');
|
}
|
if (appointment.status === '已完成') {
|
throw new CoolCommException('该预约已完成');
|
}
|
if (appointment.status === '已取消') {
|
throw new CoolCommException('该预约已取消');
|
}
|
if (
|
appointment.sorterId &&
|
String(appointment.sorterId) !== '0' &&
|
appointment.sorterId !== sorter.businessId
|
) {
|
throw new CoolCommException('该预约已被其他分拣员领取');
|
}
|
await this.recycleAppointmentEntity.update(appointment.id, {
|
sorterId: sorter.businessId,
|
});
|
await this.recycleAppointmentLogEntity.save({
|
appointmentId: appointment.id,
|
action: '接单',
|
operatorType: '分拣员',
|
operatorId: sorter.id,
|
remark: '分拣员领取预约单',
|
});
|
return this.recycleAppointmentEntity.findOneBy({ id: appointment.id });
|
}
|
|
/**
|
* 确认预约
|
*/
|
@CoolTransaction()
|
async confirmAppointment(
|
param: { id: number; sorterId: string },
|
queryRunner?: QueryRunner
|
) {
|
if (!queryRunner) {
|
throw new CoolCommException('事务启动失败');
|
}
|
const manager = queryRunner.manager;
|
const sorter = await this.getSorter(param.sorterId);
|
const appointment = await manager.findOne(RecycleAppointmentEntity, {
|
where: { id: Number(param.id) },
|
});
|
if (!appointment) {
|
throw new CoolCommException('预约单不存在');
|
}
|
if (appointment.status === '已完成') {
|
throw new CoolCommException('该预约已完成');
|
}
|
if (appointment.status === '已取消') {
|
throw new CoolCommException('该预约已取消');
|
}
|
if (appointment.status !== '已确认') {
|
await manager.update(RecycleAppointmentEntity, appointment.id, {
|
status: '已确认',
|
sorterId: sorter.businessId,
|
});
|
await manager.save(RecycleAppointmentLogEntity, {
|
appointmentId: appointment.id,
|
action: '确认',
|
operatorType: '分拣员',
|
operatorId: sorter.id,
|
remark: '分拣员确认预约',
|
});
|
}
|
return await manager.findOne(RecycleAppointmentEntity, {
|
where: { id: appointment.id },
|
});
|
}
|
|
/**
|
* 扫描预约单
|
*/
|
async scanAppointment(appointmentNo: string, sorterId: string) {
|
await this.getSorter(sorterId);
|
const no = String(appointmentNo || '').trim();
|
if (!no) {
|
throw new CoolCommException('请扫描有效的预约单');
|
}
|
const appointment = await this.recycleAppointmentEntity.findOneBy({
|
appointmentNo: no,
|
}) || (/^\d+$/.test(no)
|
? await this.recycleAppointmentEntity.findOneBy({ id: Number(no) })
|
: null);
|
if (!appointment) {
|
throw new CoolCommException('预约单不存在');
|
}
|
if (appointment.status === '已取消') {
|
throw new CoolCommException('该预约已取消');
|
}
|
if (appointment.status === '已完成') {
|
throw new CoolCommException('该预约已完成');
|
}
|
const user = appointment.campusUserId
|
? await this.checkUser(appointment.campusUserId)
|
: null;
|
return {
|
...appointment,
|
campusUser: user,
|
};
|
}
|
|
/**
|
* 生成订单编号
|
*/
|
private genOrderNo(prefix: string) {
|
const rand = Math.floor(Math.random() * 90 + 10);
|
return `${prefix}${moment().format('YYYYMMDDHHmmssSSS')}${rand}`;
|
}
|
}
|