feat(产品): 实现产品CSV导入导出功能并增强类型处理
添加产品CSV导入导出功能,支持完整产品数据包括属性和类型 扩展产品类型处理逻辑,区分simple和bundle类型的不同行为 在库存查询中增加按库存点排序功能 完善产品DTO和实体中的类型字段定义
This commit is contained in:
parent
58004dd091
commit
af9f49ab58
|
|
@ -14,6 +14,8 @@ import { errorResponse, successResponse } from '../utils/response.util';
|
||||||
import { CreateProductDTO, QueryProductDTO, UpdateProductDTO, SetProductComponentsDTO } from '../dto/product.dto';
|
import { CreateProductDTO, QueryProductDTO, UpdateProductDTO, SetProductComponentsDTO } from '../dto/product.dto';
|
||||||
import { ApiOkResponse } from '@midwayjs/swagger';
|
import { ApiOkResponse } from '@midwayjs/swagger';
|
||||||
import { BooleanRes, ProductListRes, ProductRes, ProductsRes } from '../dto/reponse.dto';
|
import { BooleanRes, ProductListRes, ProductRes, ProductsRes } from '../dto/reponse.dto';
|
||||||
|
import { ContentType, Files } from '@midwayjs/core';
|
||||||
|
import { Context } from '@midwayjs/koa';
|
||||||
|
|
||||||
@Controller('/product')
|
@Controller('/product')
|
||||||
export class ProductController {
|
export class ProductController {
|
||||||
|
|
@ -21,6 +23,9 @@ export class ProductController {
|
||||||
productService: ProductService;
|
productService: ProductService;
|
||||||
ProductRes;
|
ProductRes;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
ctx: Context;
|
||||||
|
|
||||||
@ApiOkResponse({
|
@ApiOkResponse({
|
||||||
description: '通过name搜索产品',
|
description: '通过name搜索产品',
|
||||||
type: ProductsRes,
|
type: ProductsRes,
|
||||||
|
|
@ -83,6 +88,39 @@ export class ProductController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 中文注释:导出所有产品 CSV
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Get('/export')
|
||||||
|
@ContentType('text/csv')
|
||||||
|
async exportProductsCSV() {
|
||||||
|
try {
|
||||||
|
const csv = await this.productService.exportProductsCSV();
|
||||||
|
// 设置下载文件名(中文注释:附件形式)
|
||||||
|
const date = new Date();
|
||||||
|
const pad = (n: number) => String(n).padStart(2, '0');
|
||||||
|
const name = `products-${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}.csv`;
|
||||||
|
this.ctx.set('Content-Disposition', `attachment; filename=${name}`);
|
||||||
|
return csv;
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 中文注释:导入产品(CSV 文件)
|
||||||
|
@ApiOkResponse()
|
||||||
|
@Post('/import')
|
||||||
|
async importProductsCSV(@Files() files: any) {
|
||||||
|
try {
|
||||||
|
// 条件判断:确保存在文件
|
||||||
|
const file = files?.[0];
|
||||||
|
if (!file?.data) return errorResponse('未接收到上传文件');
|
||||||
|
const result = await this.productService.importProductsCSV(file.data);
|
||||||
|
return successResponse(result);
|
||||||
|
} catch (error) {
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ApiOkResponse({ type: ProductRes })
|
@ApiOkResponse({ type: ProductRes })
|
||||||
@Put('/:id')
|
@Put('/:id')
|
||||||
async updateProduct(@Param('id') id: number, @Body() productData: UpdateProductDTO) {
|
async updateProduct(@Param('id') id: number, @Body() productData: UpdateProductDTO) {
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,11 @@ export class CreateProductDTO {
|
||||||
@ApiProperty({ description: '促销价格', example: 99.99, required: false })
|
@ApiProperty({ description: '促销价格', example: 99.99, required: false })
|
||||||
@Rule(RuleType.number())
|
@Rule(RuleType.number())
|
||||||
promotionPrice?: number;
|
promotionPrice?: number;
|
||||||
|
|
||||||
|
// 中文注释:商品类型(默认 simple;bundle 需手动设置组成)
|
||||||
|
@ApiProperty({ description: '商品类型', enum: ['simple', 'bundle'], default: 'simple', required: false })
|
||||||
|
@Rule(RuleType.string().valid('simple', 'bundle').default('simple'))
|
||||||
|
type?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -67,6 +72,11 @@ export class UpdateProductDTO {
|
||||||
@ApiProperty({ description: '属性列表', type: 'array', required: false })
|
@ApiProperty({ description: '属性列表', type: 'array', required: false })
|
||||||
@Rule(RuleType.array())
|
@Rule(RuleType.array())
|
||||||
attributes?: AttributeInputDTO[];
|
attributes?: AttributeInputDTO[];
|
||||||
|
|
||||||
|
// 中文注释:商品类型更新(simple 或 bundle)
|
||||||
|
@ApiProperty({ description: '商品类型', enum: ['simple', 'bundle'], required: false })
|
||||||
|
@Rule(RuleType.string().valid('simple', 'bundle'))
|
||||||
|
type?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,14 @@ export class QueryStockDTO {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
productName: string;
|
productName: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '按库存点ID排序', required: false })
|
||||||
|
@Rule(RuleType.number().allow(null))
|
||||||
|
sortPointId?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '排序方向', enum: ['ascend', 'descend'], required: false })
|
||||||
|
@Rule(RuleType.string().valid('ascend', 'descend').allow(''))
|
||||||
|
sortOrder?: 'ascend' | 'descend' | '';
|
||||||
}
|
}
|
||||||
export class QueryPointDTO {
|
export class QueryPointDTO {
|
||||||
@ApiProperty({ example: '1', description: '页码' })
|
@ApiProperty({ example: '1', description: '页码' })
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ export class Product {
|
||||||
price: number;
|
price: number;
|
||||||
// 类型 主要用来区分混装和单品 单品死
|
// 类型 主要用来区分混装和单品 单品死
|
||||||
@ApiProperty({ description: '类型' })
|
@ApiProperty({ description: '类型' })
|
||||||
@Column()
|
@Column({ length: 16, default: 'simple' })
|
||||||
type: string;
|
type: string;
|
||||||
// 促销价格
|
// 促销价格
|
||||||
@ApiProperty({ description: '促销价格', example: 99.99 })
|
@ApiProperty({ description: '促销价格', example: 99.99 })
|
||||||
|
|
|
||||||
|
|
@ -172,6 +172,23 @@ export class ProductService {
|
||||||
|
|
||||||
const [items, total] = await qb.getManyAndCount();
|
const [items, total] = await qb.getManyAndCount();
|
||||||
|
|
||||||
|
// 中文注释:根据类型填充组成信息
|
||||||
|
for (const p of items) {
|
||||||
|
if (p.type === 'simple') {
|
||||||
|
const stocks = await this.stockModel.find({ where: { productSku: p.sku } });
|
||||||
|
p.components = stocks.map(s => {
|
||||||
|
const comp = new ProductStockComponent();
|
||||||
|
comp.productId = p.id;
|
||||||
|
comp.stockId = s.id;
|
||||||
|
comp.quantity = 1;
|
||||||
|
comp.stock = s as any;
|
||||||
|
return comp;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
p.components = await this.productStockComponentModel.find({ where: { productId: p.id } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items,
|
items,
|
||||||
total,
|
total,
|
||||||
|
|
@ -251,6 +268,8 @@ export class ProductService {
|
||||||
product.name = name;
|
product.name = name;
|
||||||
product.description = description;
|
product.description = description;
|
||||||
product.attributes = resolvedAttributes;
|
product.attributes = resolvedAttributes;
|
||||||
|
// 条件判断(中文注释:设置商品类型,默认 simple)
|
||||||
|
product.type = (createProductDTO.type as any) || 'simple';
|
||||||
|
|
||||||
// 生成或设置 SKU(中文注释:基于属性字典项的 name 生成)
|
// 生成或设置 SKU(中文注释:基于属性字典项的 name 生成)
|
||||||
if (sku) {
|
if (sku) {
|
||||||
|
|
@ -346,6 +365,11 @@ export class ProductService {
|
||||||
product.attributes = nextAttributes;
|
product.attributes = nextAttributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 条件判断(中文注释:更新商品类型,如传入)
|
||||||
|
if (updateProductDTO.type !== undefined) {
|
||||||
|
product.type = updateProductDTO.type as any;
|
||||||
|
}
|
||||||
|
|
||||||
// 保存更新后的产品
|
// 保存更新后的产品
|
||||||
const saved = await this.productModel.save(product);
|
const saved = await this.productModel.save(product);
|
||||||
return saved;
|
return saved;
|
||||||
|
|
@ -356,6 +380,20 @@ export class ProductService {
|
||||||
// 条件判断:确保产品存在
|
// 条件判断:确保产品存在
|
||||||
const product = await this.productModel.findOne({ where: { id: productId } });
|
const product = await this.productModel.findOne({ where: { id: productId } });
|
||||||
if (!product) throw new Error(`产品 ID ${productId} 不存在`);
|
if (!product) throw new Error(`产品 ID ${productId} 不存在`);
|
||||||
|
// 条件判断(中文注释:单品 simple 不持久化组成,按 sku 动态生成)
|
||||||
|
if (product.type === 'simple') {
|
||||||
|
const stocks = await this.stockModel.find({ where: { productSku: product.sku } });
|
||||||
|
// 中文注释:将同 sku 的库存映射为组成信息(数量默认为 1)
|
||||||
|
return stocks.map(s => {
|
||||||
|
const comp = new ProductStockComponent();
|
||||||
|
comp.productId = productId;
|
||||||
|
comp.stockId = s.id;
|
||||||
|
comp.quantity = 1;
|
||||||
|
comp.stock = s as any;
|
||||||
|
return comp;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 混装 bundle:返回已保存的组成
|
||||||
return await this.productStockComponentModel.find({ where: { productId } });
|
return await this.productStockComponentModel.find({ where: { productId } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -367,6 +405,10 @@ export class ProductService {
|
||||||
// 条件判断:确保产品存在
|
// 条件判断:确保产品存在
|
||||||
const product = await this.productModel.findOne({ where: { id: productId } });
|
const product = await this.productModel.findOne({ where: { id: productId } });
|
||||||
if (!product) throw new Error(`产品 ID ${productId} 不存在`);
|
if (!product) throw new Error(`产品 ID ${productId} 不存在`);
|
||||||
|
// 条件判断(中文注释:单品 simple 不允许手动设置组成)
|
||||||
|
if (product.type === 'simple') {
|
||||||
|
throw new Error('单品无需设置组成');
|
||||||
|
}
|
||||||
|
|
||||||
const validItems = (items || [])
|
const validItems = (items || [])
|
||||||
.filter(i => i && i.stockId && i.quantity && i.quantity > 0)
|
.filter(i => i && i.stockId && i.quantity && i.quantity > 0)
|
||||||
|
|
@ -395,24 +437,35 @@ export class ProductService {
|
||||||
// 条件判断:确保产品存在
|
// 条件判断:确保产品存在
|
||||||
const product = await this.productModel.findOne({ where: { id: productId } });
|
const product = await this.productModel.findOne({ where: { id: productId } });
|
||||||
if (!product) throw new Error(`产品 ID ${productId} 不存在`);
|
if (!product) throw new Error(`产品 ID ${productId} 不存在`);
|
||||||
|
|
||||||
const stocks = await this.stockModel.find({ where: { productSku: product.sku } });
|
const stocks = await this.stockModel.find({ where: { productSku: product.sku } });
|
||||||
if (stocks.length === 0) return [];
|
if (stocks.length === 0) return [];
|
||||||
|
// 条件判断(中文注释:simple 类型不持久化组成,直接返回动态映射)
|
||||||
|
if (product.type === 'simple') {
|
||||||
|
return stocks.map(s => {
|
||||||
|
const comp = new ProductStockComponent();
|
||||||
|
comp.productId = productId;
|
||||||
|
comp.stockId = s.id;
|
||||||
|
comp.quantity = 1; // 默认数量 1
|
||||||
|
comp.stock = s as any;
|
||||||
|
return comp;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// bundle 类型:持久化组成
|
||||||
for (const stock of stocks) {
|
for (const stock of stocks) {
|
||||||
// 条件判断:若已存在相同 stockId 的组成则跳过
|
|
||||||
const exist = await this.productStockComponentModel.findOne({ where: { productId, stockId: stock.id } });
|
const exist = await this.productStockComponentModel.findOne({ where: { productId, stockId: stock.id } });
|
||||||
if (exist) continue;
|
if (exist) continue;
|
||||||
const comp = new ProductStockComponent();
|
const comp = new ProductStockComponent();
|
||||||
comp.productId = productId;
|
comp.productId = productId;
|
||||||
comp.stockId = stock.id;
|
comp.stockId = stock.id;
|
||||||
comp.quantity = 1; // 默认数量 1
|
comp.quantity = 1;
|
||||||
comp.stock = stock;
|
comp.stock = stock as any;
|
||||||
await this.productStockComponentModel.save(comp);
|
await this.productStockComponentModel.save(comp);
|
||||||
}
|
}
|
||||||
return await this.getProductComponents(productId);
|
return await this.getProductComponents(productId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 重复定义的 getProductList 已合并到前面的实现(中文注释:移除重复)
|
||||||
|
|
||||||
async updateProductNameCn(id: number, nameCn: string): Promise<Product> {
|
async updateProductNameCn(id: number, nameCn: string): Promise<Product> {
|
||||||
// 确认产品是否存在
|
// 确认产品是否存在
|
||||||
const product = await this.productModel.findOneBy({ id });
|
const product = await this.productModel.findOneBy({ id });
|
||||||
|
|
@ -901,4 +954,170 @@ export class ProductService {
|
||||||
|
|
||||||
return `成功更新 ${skus.length} 个 sku`;
|
return `成功更新 ${skus.length} 个 sku`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 中文注释:导出所有产品为 CSV 文本
|
||||||
|
async exportProductsCSV(): Promise<string> {
|
||||||
|
// 查询所有产品及其属性(中文注释:包含字典关系)
|
||||||
|
const products = await this.productModel.find({
|
||||||
|
relations: ['attributes', 'attributes.dict'],
|
||||||
|
order: { id: 'ASC' },
|
||||||
|
});
|
||||||
|
|
||||||
|
// 定义 CSV 表头(中文注释:与导入字段一致)
|
||||||
|
const headers = [
|
||||||
|
'sku',
|
||||||
|
'name',
|
||||||
|
'nameCn',
|
||||||
|
'price',
|
||||||
|
'promotionPrice',
|
||||||
|
'type',
|
||||||
|
'stock',
|
||||||
|
'brand',
|
||||||
|
'flavor',
|
||||||
|
'strength',
|
||||||
|
'size',
|
||||||
|
'description',
|
||||||
|
];
|
||||||
|
|
||||||
|
// 中文注释:CSV 字段转义,处理逗号与双引号
|
||||||
|
const esc = (v: any) => {
|
||||||
|
const s = v === undefined || v === null ? '' : String(v);
|
||||||
|
const needsQuote = /[",\n]/.test(s);
|
||||||
|
const escaped = s.replace(/"/g, '""');
|
||||||
|
return needsQuote ? `"${escaped}"` : escaped;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 中文注释:将属性列表转为字典名到显示值的映射
|
||||||
|
const pickAttr = (prod: Product, key: string) => {
|
||||||
|
const list = (prod.attributes || []).filter(a => a?.dict?.name === key);
|
||||||
|
if (list.length === 0) return '';
|
||||||
|
// 多个值使用分号分隔
|
||||||
|
return list.map(a => a.title || a.name).join(';');
|
||||||
|
};
|
||||||
|
|
||||||
|
const rows: string[] = [];
|
||||||
|
rows.push(headers.join(','));
|
||||||
|
for (const p of products) {
|
||||||
|
// 中文注释:逐行输出产品数据
|
||||||
|
const row = [
|
||||||
|
esc(p.sku),
|
||||||
|
esc(p.name),
|
||||||
|
esc(p.nameCn),
|
||||||
|
esc(p.price),
|
||||||
|
esc(p.promotionPrice),
|
||||||
|
esc(p.type),
|
||||||
|
esc(p.stock),
|
||||||
|
esc(pickAttr(p, 'brand')),
|
||||||
|
esc(pickAttr(p, 'flavor')),
|
||||||
|
esc(pickAttr(p, 'strength')),
|
||||||
|
esc(pickAttr(p, 'size')),
|
||||||
|
esc(p.description),
|
||||||
|
].join(',');
|
||||||
|
rows.push(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 中文注释:从 CSV 导入产品;存在则更新,不存在则创建
|
||||||
|
async importProductsCSV(buffer: Buffer): Promise<{ created: number; updated: number; errors: string[] }> {
|
||||||
|
// 解析 CSV(中文注释:使用 csv-parse/sync 按表头解析)
|
||||||
|
const { parse } = await import('csv-parse/sync');
|
||||||
|
let records: any[] = [];
|
||||||
|
try {
|
||||||
|
records = parse(buffer, {
|
||||||
|
columns: true,
|
||||||
|
skip_empty_lines: true,
|
||||||
|
trim: true,
|
||||||
|
});
|
||||||
|
} catch (e: any) {
|
||||||
|
return { created: 0, updated: 0, errors: [`CSV 解析失败:${e?.message || e}`] };
|
||||||
|
}
|
||||||
|
|
||||||
|
let created = 0;
|
||||||
|
let updated = 0;
|
||||||
|
const errors: string[] = [];
|
||||||
|
|
||||||
|
// 中文注释:逐条处理记录
|
||||||
|
for (const rec of records) {
|
||||||
|
try {
|
||||||
|
// 条件判断:必须包含 sku
|
||||||
|
const sku: string = (rec.sku || '').trim();
|
||||||
|
if (!sku) {
|
||||||
|
// 缺少 SKU 直接跳过
|
||||||
|
errors.push('缺少 SKU 的记录已跳过');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找现有产品
|
||||||
|
const exist = await this.productModel.findOne({ where: { sku }, relations: ['attributes', 'attributes.dict'] });
|
||||||
|
|
||||||
|
// 中文注释:准备基础字段
|
||||||
|
const base = {
|
||||||
|
name: rec.name || '',
|
||||||
|
nameCn: rec.nameCn || '',
|
||||||
|
description: rec.description || '',
|
||||||
|
price: rec.price ? Number(rec.price) : undefined,
|
||||||
|
promotionPrice: rec.promotionPrice ? Number(rec.promotionPrice) : undefined,
|
||||||
|
type: rec.type || '',
|
||||||
|
stock: rec.stock ? Number(rec.stock) : undefined,
|
||||||
|
sku,
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
// 中文注释:解析属性字段(分号分隔多值)
|
||||||
|
const parseList = (v: string) => (v ? String(v).split(';').map(s => s.trim()).filter(Boolean) : []);
|
||||||
|
const brands = parseList(rec.brand);
|
||||||
|
const flavors = parseList(rec.flavor);
|
||||||
|
const strengths = parseList(rec.strength);
|
||||||
|
const sizes = parseList(rec.size);
|
||||||
|
|
||||||
|
// 中文注释:将属性解析为 DTO 输入
|
||||||
|
const attrDTOs: { dictName: string; title: string }[] = [];
|
||||||
|
for (const b of brands) attrDTOs.push({ dictName: 'brand', title: b });
|
||||||
|
for (const f of flavors) attrDTOs.push({ dictName: 'flavor', title: f });
|
||||||
|
for (const s of strengths) attrDTOs.push({ dictName: 'strength', title: s });
|
||||||
|
for (const z of sizes) attrDTOs.push({ dictName: 'size', title: z });
|
||||||
|
|
||||||
|
if (!exist) {
|
||||||
|
// 中文注释:创建新产品
|
||||||
|
const dto = {
|
||||||
|
name: base.name,
|
||||||
|
description: base.description,
|
||||||
|
price: base.price,
|
||||||
|
sku: base.sku,
|
||||||
|
attributes: attrDTOs,
|
||||||
|
} as any;
|
||||||
|
const createdProduct = await this.createProduct(dto);
|
||||||
|
// 条件判断:更新可选字段
|
||||||
|
const patch: any = {};
|
||||||
|
if (base.nameCn) patch.nameCn = base.nameCn;
|
||||||
|
if (base.promotionPrice !== undefined) patch.promotionPrice = base.promotionPrice;
|
||||||
|
if (base.type) patch.type = base.type;
|
||||||
|
if (base.stock !== undefined) patch.stock = base.stock;
|
||||||
|
if (Object.keys(patch).length > 0) await this.productModel.update(createdProduct.id, patch);
|
||||||
|
created += 1;
|
||||||
|
} else {
|
||||||
|
// 中文注释:更新产品
|
||||||
|
const updateDTO: any = {
|
||||||
|
name: base.name || exist.name,
|
||||||
|
description: base.description || exist.description,
|
||||||
|
price: base.price !== undefined ? base.price : exist.price,
|
||||||
|
sku: base.sku,
|
||||||
|
attributes: attrDTOs,
|
||||||
|
};
|
||||||
|
// 条件判断:附加可选字段
|
||||||
|
if (base.nameCn) updateDTO.nameCn = base.nameCn;
|
||||||
|
if (base.promotionPrice !== undefined) updateDTO.promotionPrice = base.promotionPrice;
|
||||||
|
if (base.type) updateDTO.type = base.type;
|
||||||
|
if (base.stock !== undefined) updateDTO.stock = base.stock;
|
||||||
|
await this.updateProduct(exist.id, updateDTO);
|
||||||
|
updated += 1;
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
errors.push(e?.message || String(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { created, updated, errors };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -230,7 +230,7 @@ export class StockService {
|
||||||
|
|
||||||
// 获取库存列表
|
// 获取库存列表
|
||||||
async getStocks(query: QueryStockDTO) {
|
async getStocks(query: QueryStockDTO) {
|
||||||
const { current = 1, pageSize = 10, productName } = query;
|
const { current = 1, pageSize = 10, productName, sortPointId, sortOrder } = query;
|
||||||
const nameKeywords = productName
|
const nameKeywords = productName
|
||||||
? productName.split(' ').filter(Boolean)
|
? productName.split(' ').filter(Boolean)
|
||||||
: [];
|
: [];
|
||||||
|
|
@ -274,6 +274,12 @@ export class StockService {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (sortPointId && sortOrder) {
|
||||||
|
const sortExpr = `SUM(CASE WHEN stock.stockPointId = :sortPointId THEN stock.quantity ELSE 0 END)`;
|
||||||
|
queryBuilder.addSelect(sortExpr, 'pointSort').setParameter('sortPointId', Number(sortPointId));
|
||||||
|
queryBuilder.orderBy('pointSort', sortOrder === 'ascend' ? 'ASC' : 'DESC');
|
||||||
|
}
|
||||||
|
|
||||||
const items = await queryBuilder.getRawMany();
|
const items = await queryBuilder.getRawMany();
|
||||||
const total = await totalQueryBuilder.getRawOne();
|
const total = await totalQueryBuilder.getRawOne();
|
||||||
const transfer = await this.transferModel
|
const transfer = await this.transferModel
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue