From a5996363c868e4f92a7dfed60c1a7bfefa00fce0 Mon Sep 17 00:00:00 2001 From: tikkhun Date: Sun, 30 Nov 2025 15:57:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=BA=93=E5=AD=98):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E5=BA=93=E5=AD=98=E7=BB=84=E4=BB=B6=E4=B8=BA=E5=9F=BA=E4=BA=8E?= =?UTF-8?q?SKU=E7=9A=84=E8=AE=BE=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将库存组件从基于stockId改为基于productSku - 添加检查SKU库存的接口 - 改进库存查询支持多字段排序 - 优化产品组件查询返回库存点信息 - 允许字典项titleCN为空字符串 --- src/controller/product.controller.ts | 8 +- src/controller/stock.controller.ts | 12 ++ src/dto/dict.dto.ts | 4 +- src/dto/product.dto.ts | 6 +- src/dto/stock.dto.ts | 10 +- src/entity/product_stock_component.entity.ts | 12 +- src/service/dict.service.ts | 7 +- src/service/product.service.ts | 141 ++++++++++++------- src/service/stock.service.ts | 65 ++++++++- tsconfig.tsbuildinfo | 1 + 10 files changed, 184 insertions(+), 82 deletions(-) create mode 100644 tsconfig.tsbuildinfo diff --git a/src/controller/product.controller.ts b/src/controller/product.controller.ts index 754156f..50f0e4f 100644 --- a/src/controller/product.controller.ts +++ b/src/controller/product.controller.ts @@ -232,12 +232,8 @@ export class ProductController { @Body() body: { title: string; name: string } ) { try { - const hasItem = await this.productService.hasAttribute( - dictName, - body.name - ); - if (hasItem) return errorResponse('字典项已存在'); - const data = await this.productService.createAttribute(dictName, body); + // 调用 getOrCreateAttribute 方法,如果不存在则创建,如果存在则返回 + const data = await this.productService.getOrCreateAttribute(dictName, body.title, body.name); return successResponse(data); } catch (error) { return errorResponse(error?.message || error); diff --git a/src/controller/stock.controller.ts b/src/controller/stock.controller.ts index a407df0..b3417ec 100644 --- a/src/controller/stock.controller.ts +++ b/src/controller/stock.controller.ts @@ -176,6 +176,18 @@ export class StockController { } } + // 中文注释:检查某个 SKU 是否有库存(任一仓库数量大于 0) + @ApiOkResponse({ type: BooleanRes }) + @Get('/has/:sku') + async hasStock(@Param('sku') sku: string) { + try { + const data = await this.stockService.hasStockBySku(sku); + return successResponse(data); + } catch (error) { + return errorResponse(error?.message || '查询失败'); + } + } + @ApiOkResponse({ type: BooleanRes, description: '更新库存(入库、出库、调整)', diff --git a/src/dto/dict.dto.ts b/src/dto/dict.dto.ts index b3f015e..a6c8f7e 100644 --- a/src/dto/dict.dto.ts +++ b/src/dto/dict.dto.ts @@ -26,7 +26,7 @@ export class CreateDictItemDTO { @Rule(RuleType.string().required()) title: string; // 字典项标题 - @Rule(RuleType.string().allow(null)) + @Rule(RuleType.string().allow('').allow(null)) titleCN?: string; // 字典项中文标题 (可选) @Rule(RuleType.number().required()) @@ -41,7 +41,7 @@ export class UpdateDictItemDTO { @Rule(RuleType.string()) title?: string; // 字典项标题 (可选) - @Rule(RuleType.string().allow(null)) + @Rule(RuleType.string().allow('').allow(null)) titleCN?: string; // 字典项中文标题 (可选) @Rule(RuleType.string().allow(null)) diff --git a/src/dto/product.dto.ts b/src/dto/product.dto.ts index becabc9..166ff63 100644 --- a/src/dto/product.dto.ts +++ b/src/dto/product.dto.ts @@ -284,9 +284,9 @@ export class BatchSetSkuDTO { // 中文注释:产品库存组成项输入 export class ProductComponentItemDTO { - @ApiProperty({ description: '库存记录ID' }) - @Rule(RuleType.number().required()) - stockId: number; + @ApiProperty({ description: '组件 SKU' }) + @Rule(RuleType.string().required()) + sku: string; @ApiProperty({ description: '组成数量', example: 1 }) @Rule(RuleType.number().min(1).default(1)) diff --git a/src/dto/stock.dto.ts b/src/dto/stock.dto.ts index 353ff18..84b2d4e 100644 --- a/src/dto/stock.dto.ts +++ b/src/dto/stock.dto.ts @@ -22,13 +22,17 @@ export class QueryStockDTO { @Rule(RuleType.string()) productName: string; + @ApiProperty() + @Rule(RuleType.string()) + productSku: 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' | ''; + @ApiProperty({ description: '排序对象,格式如 { productName: "asc", productSku: "desc" }', required: false }) + @Rule(RuleType.object().allow(null)) + order?: Record; } export class QueryPointDTO { @ApiProperty({ example: '1', description: '页码' }) diff --git a/src/entity/product_stock_component.entity.ts b/src/entity/product_stock_component.entity.ts index 10a12df..3985e88 100644 --- a/src/entity/product_stock_component.entity.ts +++ b/src/entity/product_stock_component.entity.ts @@ -1,7 +1,6 @@ import { ApiProperty } from '@midwayjs/swagger'; import { Column, CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; import { Product } from './product.entity'; -import { Stock } from './stock.entity'; @Entity('product_stock_component') export class ProductStockComponent { @@ -13,9 +12,9 @@ export class ProductStockComponent { @Column() productId: number; - @ApiProperty({ type: Number }) - @Column() - stockId: number; + @ApiProperty({ description: '组件所关联的 SKU', type: 'string' }) + @Column({ type: 'varchar', length: 64 }) + productSku: string; @ApiProperty({ type: Number, description: '组成数量' }) @Column({ type: 'int', default: 1 }) @@ -25,10 +24,6 @@ export class ProductStockComponent { @ManyToOne(() => Product, (product) => product.components, { onDelete: 'CASCADE' }) product: Product; - // 中文注释:多对一,组件引用一个库存记录 - @ManyToOne(() => Stock, { eager: true, onDelete: 'CASCADE' }) - stock: Stock; - @ApiProperty({ description: '创建时间' }) @CreateDateColumn() createdAt: Date; @@ -37,4 +32,3 @@ export class ProductStockComponent { @UpdateDateColumn() updatedAt: Date; } - diff --git a/src/service/dict.service.ts b/src/service/dict.service.ts index 6facfd6..43c9177 100644 --- a/src/service/dict.service.ts +++ b/src/service/dict.service.ts @@ -55,7 +55,7 @@ export class DictService { // 生成并返回字典项的XLSX模板 getDictItemXLSXTemplate() { - const headers = ['name', 'title', 'value', 'sort']; + const headers = ['name', 'title', 'titleCN', 'value', 'sort']; const ws = xlsx.utils.aoa_to_sheet([headers]); const wb = xlsx.utils.book_new(); xlsx.utils.book_append_sheet(wb, ws, 'DictItems'); @@ -71,12 +71,14 @@ export class DictService { const wb = xlsx.read(buffer, { type: 'buffer' }); const wsname = wb.SheetNames[0]; const ws = wb.Sheets[wsname]; - const data = xlsx.utils.sheet_to_json(ws, { header: ['name', 'title', 'value', 'sort'] }).slice(1); + // 支持titleCN字段的导入 + const data = xlsx.utils.sheet_to_json(ws, { header: ['name', 'title', 'titleCN', 'value', 'sort'] }).slice(1); const items = data.map((row: any) => { const item = new DictItem(); item.name = row.name; item.title = row.title; + item.titleCN = row.titleCN; // 保存中文名称 item.value = row.value; item.sort = row.sort || 0; item.dict = dict; @@ -143,6 +145,7 @@ export class DictService { const item = new DictItem(); item.name = createDictItemDTO.name; item.title = createDictItemDTO.title; + item.titleCN = createDictItemDTO.titleCN; // 保存中文名称 item.dict = dict; return this.dictItemModel.save(item); } diff --git a/src/service/product.service.ts b/src/service/product.service.ts index c76e9a7..4367ab8 100644 --- a/src/service/product.service.ts +++ b/src/service/product.service.ts @@ -31,6 +31,7 @@ import { Context } from '@midwayjs/koa'; import { TemplateService } from './template.service'; import { StockService } from './stock.service'; import { Stock } from '../entity/stock.entity'; +import { StockPoint } from '../entity/stock_point.entity'; import { ProductStockComponent } from '../entity/product_stock_component.entity'; @Provide() @@ -62,6 +63,9 @@ export class ProductService { @InjectEntityModel(Stock) stockModel: Repository; + @InjectEntityModel(StockPoint) + stockPointModel: Repository; + @InjectEntityModel(ProductStockComponent) productStockComponentModel: Repository; @@ -175,16 +179,14 @@ export class ProductService { // 中文注释:根据类型填充组成信息 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; - }); + // 中文注释:单品不持久化组成,这里仅返回一个基于 SKU 的虚拟组成 + const comp = new ProductStockComponent(); + comp.productId = p.id; + comp.productSku = p.sku; + comp.quantity = 1; + p.components = [comp]; } else { + // 中文注释:混装商品返回持久化的 SKU 组成 p.components = await this.productStockComponentModel.find({ where: { productId: p.id } }); } } @@ -207,16 +209,18 @@ export class ProductService { throw new Error(`字典 '${dictName}' 不存在`); } + const nameForLookup = itemName || itemTitle; + // 查找字典项 let item = await this.dictItemModel.findOne({ - where: { title: itemTitle, dict: { id: dict.id } }, + where: { name: nameForLookup, dict: { id: dict.id } }, }); // 如果字典项不存在,则创建 if (!item) { item = new DictItem(); item.title = itemTitle; - item.name = itemName || itemTitle; // 如果没有提供 name,则使用 title + item.name = nameForLookup; item.dict = dict; await this.dictItemModel.save(item); } @@ -376,31 +380,73 @@ export class ProductService { } // 中文注释:获取产品的库存组成列表(表关联版本) - async getProductComponents(productId: number): Promise { + async getProductComponents(productId: number): Promise { // 条件判断:确保产品存在 const product = await this.productModel.findOne({ where: { id: productId } }); if (!product) throw new Error(`产品 ID ${productId} 不存在`); - // 条件判断(中文注释:单品 simple 不持久化组成,按 sku 动态生成) + + let components: ProductStockComponent[] = []; + // 条件判断(中文注释:单品 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; - }); + const comp = new ProductStockComponent(); + comp.productId = productId; + comp.productSku = product.sku; + comp.quantity = 1; + components = [comp]; + } else { + // 混装 bundle:返回已保存的 SKU 组成 + components = await this.productStockComponentModel.find({ where: { productId } }); } - // 混装 bundle:返回已保存的组成 - return await this.productStockComponentModel.find({ where: { productId } }); + + // 中文注释:获取所有组件的 SKU 列表 + const skus = components.map(c => c.productSku); + if (skus.length === 0) { + return components; + } + + // 中文注释:查询这些 SKU 的库存信息 + const stocks = await this.stockModel.find({ + where: { productSku: In(skus) }, + }); + + // 中文注释:获取所有相关的库存点 ID + const stockPointIds = [...new Set(stocks.map(s => s.stockPointId))]; + const stockPoints = await this.stockPointModel.find({ where: { id: In(stockPointIds) } }); + const stockPointMap = stockPoints.reduce((map, sp) => { + map[sp.id] = sp; + return map; + }, {}); + + // 中文注释:将库存信息按 SKU 分组 + const stockMap = stocks.reduce((map, stock) => { + if (!map[stock.productSku]) { + map[stock.productSku] = []; + } + const stockPoint = stockPointMap[stock.stockPointId]; + if (stockPoint) { + map[stock.productSku].push({ + name: stockPoint.name, + quantity: stock.quantity, + }); + } + return map; + }, {}); + + // 中文注释:将库存信息附加到组件上 + const componentsWithStock = components.map(comp => { + return { + ...comp, + stock: stockMap[comp.productSku] || [], + }; + }); + + return componentsWithStock; } // 中文注释:设置产品的库存组成(覆盖式,表关联版本) async setProductComponents( productId: number, - items: { stockId: number; quantity: number }[] + items: { sku: string; quantity: number }[] ): Promise { // 条件判断:确保产品存在 const product = await this.productModel.findOne({ where: { id: productId } }); @@ -411,8 +457,8 @@ export class ProductService { } const validItems = (items || []) - .filter(i => i && i.stockId && i.quantity && i.quantity > 0) - .map(i => ({ stockId: Number(i.stockId), quantity: Number(i.quantity) })); + .filter(i => i && i.sku && i.quantity && i.quantity > 0) + .map(i => ({ sku: String(i.sku), quantity: Number(i.quantity) })); // 删除旧的组成 await this.productStockComponentModel.delete({ productId }); @@ -420,13 +466,14 @@ export class ProductService { // 插入新的组成 const created: ProductStockComponent[] = []; for (const i of validItems) { - const stock = await this.stockModel.findOne({ where: { id: i.stockId } }); - if (!stock) throw new Error(`库存 ID ${i.stockId} 不存在`); + // 中文注释:校验 SKU 格式,允许不存在库存但必须非空 + if (!i.sku || i.sku.trim().length === 0) { + throw new Error('SKU 不能为空'); + } const comp = new ProductStockComponent(); comp.productId = productId; - comp.stockId = i.stockId; + comp.productSku = i.sku; comp.quantity = i.quantity; - comp.stock = stock; created.push(await this.productStockComponentModel.save(comp)); } return created; @@ -437,28 +484,22 @@ export class ProductService { // 条件判断:确保产品存在 const product = await this.productModel.findOne({ where: { id: productId } }); if (!product) throw new Error(`产品 ID ${productId} 不存在`); - const stocks = await this.stockModel.find({ where: { productSku: product.sku } }); - if (stocks.length === 0) return []; - // 条件判断(中文注释:simple 类型不持久化组成,直接返回动态映射) + // 中文注释:按 SKU 自动绑定 + // 条件判断:simple 类型不持久化组成,直接返回单条基于 SKU 的组成 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) { - const exist = await this.productStockComponentModel.findOne({ where: { productId, stockId: stock.id } }); - if (exist) continue; const comp = new ProductStockComponent(); comp.productId = productId; - comp.stockId = stock.id; + comp.productSku = product.sku; + comp.quantity = 1; // 默认数量 1 + return [comp]; + } + // bundle 类型:若不存在则持久化一条基于 SKU 的组成 + const exist = await this.productStockComponentModel.findOne({ where: { productId, productSku: product.sku } }); + if (!exist) { + const comp = new ProductStockComponent(); + comp.productId = productId; + comp.productSku = product.sku; comp.quantity = 1; - comp.stock = stock as any; await this.productStockComponentModel.save(comp); } return await this.getProductComponents(productId); diff --git a/src/service/stock.service.ts b/src/service/stock.service.ts index 24d214e..db9bf31 100644 --- a/src/service/stock.service.ts +++ b/src/service/stock.service.ts @@ -187,6 +187,16 @@ export class StockService { ); } + // 中文注释:检查指定 SKU 是否在任一仓库有库存(数量大于 0) + async hasStockBySku(sku: string): Promise { + const count = await this.stockModel + .createQueryBuilder('stock') + .where('stock.productSku = :sku', { sku }) + .andWhere('stock.quantity > 0') + .getCount(); + return count > 0; + } + async delPurchaseOrder(id: number) { const purchaseOrder = await this.purchaseOrderModel.findOneBy({ id }); if (!purchaseOrder) throw new Error(`采购订单 ID ${id} 不存在`); @@ -230,7 +240,7 @@ export class StockService { // 获取库存列表 async getStocks(query: QueryStockDTO) { - const { current = 1, pageSize = 10, productName, sortPointId, sortOrder } = query; + const { current = 1, pageSize = 10, productName, productSku } = query; const nameKeywords = productName ? productName.split(' ').filter(Boolean) : []; @@ -254,6 +264,10 @@ export class StockService { .createQueryBuilder('stock') .select('COUNT(DISTINCT stock.productSku)', 'count') .leftJoin(Product, 'product', 'product.sku = stock.productSku'); + if (productSku) { + queryBuilder.andWhere('stock.productSku = :productSku', { productSku }); + totalQueryBuilder.andWhere('stock.productSku = :productSku', { productSku }); + } if (nameKeywords.length) { nameKeywords.forEach((name, index) => { queryBuilder.andWhere( @@ -274,14 +288,51 @@ 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'); + if (query.order) { + const sortFieldMap: Record = { + productName: 'product.name', + productSku: 'stock.productSku', + updatedAt: 'updatedAt', + createdAt: 'createdAt', + }; + let isFirstSort = true; + Object.entries(query.order).forEach(([field, direction]) => { + const orderDirection = direction === 'asc' ? 'ASC' : 'DESC'; + if (field.startsWith('point_')) { + const pointId = field.split('_')[1]; + const sortExpr = `SUM(CASE WHEN stock.stockPointId = :pointId THEN stock.quantity ELSE 0 END)`; + const sortAlias = `pointSort_${pointId}`; + queryBuilder + .addSelect(sortExpr, sortAlias) + .setParameter('pointId', Number(pointId)); + if (isFirstSort) { + queryBuilder.orderBy(sortAlias, orderDirection); + isFirstSort = false; + } else { + queryBuilder.addOrderBy(sortAlias, orderDirection); + } + } else { + const actualSortField = sortFieldMap[field] || field; + if (isFirstSort) { + queryBuilder.orderBy(actualSortField, orderDirection); + isFirstSort = false; + } else { + queryBuilder.addOrderBy(actualSortField, orderDirection); + } + } + }); + } else { + // 默认按产品名称排序 + queryBuilder.orderBy('product.name', 'ASC'); } - const items = await queryBuilder.getRawMany(); - const total = await totalQueryBuilder.getRawOne(); + const items = await queryBuilder + .offset((current - 1) * pageSize) + .limit(pageSize) + .getRawMany(); + const totalResult = await totalQueryBuilder.getRawOne(); + const total = parseInt(totalResult.count, 10); + const transfer = await this.transferModel .createQueryBuilder('t') .select(['ti.productSku as productSku', 'SUM(ti.quantity) as quantity']) diff --git a/tsconfig.tsbuildinfo b/tsconfig.tsbuildinfo new file mode 100644 index 0000000..5924f59 --- /dev/null +++ b/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/configuration.ts","./src/interface.ts","./src/config/config.default.ts","./src/config/config.local.ts","./src/config/config.unittest.ts","./src/controller/api.controller.ts","./src/controller/area.controller.ts","./src/controller/customer.controller.ts","./src/controller/dict.controller.ts","./src/controller/locale.controller.ts","./src/controller/logistics.controller.ts","./src/controller/order.controller.ts","./src/controller/product.controller.ts","./src/controller/site.controller.ts","./src/controller/statistics.controller.ts","./src/controller/stock.controller.ts","./src/controller/subscription.controller.ts","./src/controller/template.controller.ts","./src/controller/user.controller.ts","./src/controller/webhook.controller.ts","./src/controller/wp_product.controller.ts","./src/db/datasource.ts","./src/db/migrations/1764238434984-product-dict-item-many-to-many.ts","./src/db/migrations/1764294088896-area.ts","./src/db/migrations/1764299629279-productstock.ts","./src/db/seeds/area.seeder.ts","./src/db/seeds/dict.seeder.ts","./src/db/seeds/template.seeder.ts","./src/decorator/user.decorator.ts","./src/dto/area.dto.ts","./src/dto/customer.dto.ts","./src/dto/dict.dto.ts","./src/dto/freightcom.dto.ts","./src/dto/logistics.dto.ts","./src/dto/order.dto.ts","./src/dto/product.dto.ts","./src/dto/reponse.dto.ts","./src/dto/site.dto.ts","./src/dto/statistics.dto.ts","./src/dto/stock.dto.ts","./src/dto/subscription.dto.ts","./src/dto/template.dto.ts","./src/dto/user.dto.ts","./src/dto/wp_product.dto.ts","./src/entity/area.entity.ts","./src/entity/auth_code.ts","./src/entity/customer.entity.ts","./src/entity/customer_tag.entity.ts","./src/entity/device_whitelist.ts","./src/entity/dict.entity.ts","./src/entity/dict_item.entity.ts","./src/entity/order.entity.ts","./src/entity/order_coupon.entity.ts","./src/entity/order_fee.entity.ts","./src/entity/order_item.entity.ts","./src/entity/order_item_original.entity.ts","./src/entity/order_items_original.entity.ts","./src/entity/order_note.entity.ts","./src/entity/order_refund.entity.ts","./src/entity/order_refund_item.entity.ts","./src/entity/order_sale.entity.ts","./src/entity/order_shipment.entity.ts","./src/entity/order_shipping.entity.ts","./src/entity/product.entity.ts","./src/entity/product_stock_component.entity.ts","./src/entity/purchase_order.entity.ts","./src/entity/purchase_order_item.entity.ts","./src/entity/service.entity.ts","./src/entity/shipment.entity.ts","./src/entity/shipment_item.entity.ts","./src/entity/shipping_address.entity.ts","./src/entity/site.entity.ts","./src/entity/stock.entity.ts","./src/entity/stock_point.entity.ts","./src/entity/stock_record.entity.ts","./src/entity/subscription.entity.ts","./src/entity/template.entity.ts","./src/entity/transfer.entity.ts","./src/entity/transfer_item.entity.ts","./src/entity/user.entity.ts","./src/entity/variation.entity.ts","./src/entity/wp_product.entity.ts","./src/enums/base.enum.ts","./src/filter/default.filter.ts","./src/filter/notfound.filter.ts","./src/job/sync_products.job.ts","./src/job/sync_shipment.job.ts","./src/middleware/auth.middleware.ts","./src/middleware/report.middleware.ts","./src/service/area.service.ts","./src/service/authcode.service.ts","./src/service/canadapost.service.ts","./src/service/customer.service.ts","./src/service/devicewhitelist.service.ts","./src/service/dict.service.ts","./src/service/freightcom.service.ts","./src/service/logistics.service.ts","./src/service/mail.service.ts","./src/service/order.service.ts","./src/service/product.service.ts","./src/service/site.service.ts","./src/service/statistics.service.ts","./src/service/stock.service.ts","./src/service/subscription.service.ts","./src/service/template.service.ts","./src/service/uni_express.service.ts","./src/service/user.service.ts","./src/service/wp.service.ts","./src/service/wp_product.service.ts","./src/utils/helper.util.ts","./src/utils/object-transform.util.ts","./src/utils/paginate.util.ts","./src/utils/paginated-response.util.ts","./src/utils/response-wrapper.util.ts","./src/utils/response.util.ts"],"version":"5.9.3"} \ No newline at end of file