refactor(实体和服务): 统一将productSku字段重命名为sku
重构实体类和服务层代码,将productSku字段统一更名为sku以保持命名一致性 修改涉及库存、订单、采购等多个模块的查询和更新逻辑 同时将产品类型默认值从simple改为single,并优化相关条件判断
This commit is contained in:
parent
8b31da07a0
commit
e4fc195b8d
|
|
@ -0,0 +1,24 @@
|
|||
|
||||
> my-midway-project@1.0.0 dev
|
||||
> cross-env NODE_ENV=local mwtsc --watch --run @midwayjs/mock/app.js
|
||||
|
||||
[2J[3J[H
|
||||
[[90m10:37:17 AM[0m] Starting compilation in watch mode...
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[[90m10:37:19 AM[0m] Found 0 errors. Watching for file changes.
|
||||
|
||||
2025-12-01 10:37:20.106 INFO 58678 [SyncProductJob] start job SyncProductJob
|
||||
2025-12-01 10:37:20.106 INFO 58678 [SyncShipmentJob] start job SyncShipmentJob
|
||||
2025-12-01 10:37:20.109 INFO 58678 [SyncProductJob] complete job SyncProductJob
|
||||
|
||||
[32mNode.js server[0m [2mstarted in[0m 732 ms
|
||||
|
||||
[32m➜[0m Local: [36mhttp://127.0.0.1:[1m7001[0m[36m/[0m[0m
|
||||
[32m➜[0m [2mNetwork: http://192.168.5.100:7001/ [0m
|
||||
|
||||
2025-12-01 10:37:20.110 INFO 58678 [SyncShipmentJob] complete job SyncShipmentJob
|
||||
|
|
@ -24,13 +24,13 @@ export class QueryStockDTO {
|
|||
|
||||
@ApiProperty()
|
||||
@Rule(RuleType.string())
|
||||
productSku: string;
|
||||
sku: string;
|
||||
|
||||
@ApiProperty({ description: '按库存点ID排序', required: false })
|
||||
@Rule(RuleType.number().allow(null))
|
||||
sortPointId?: number;
|
||||
|
||||
@ApiProperty({ description: '排序对象,格式如 { productName: "asc", productSku: "desc" }', required: false })
|
||||
@ApiProperty({ description: '排序对象,格式如 { productName: "asc", sku: "desc" }', required: false })
|
||||
@Rule(RuleType.object().allow(null))
|
||||
order?: Record<string, 'asc' | 'desc'>;
|
||||
}
|
||||
|
|
@ -58,7 +58,7 @@ export class QueryStockRecordDTO {
|
|||
|
||||
@ApiProperty()
|
||||
@Rule(RuleType.string())
|
||||
productSku: string;
|
||||
sku: string;
|
||||
|
||||
@ApiProperty()
|
||||
@Rule(RuleType.string())
|
||||
|
|
@ -132,7 +132,7 @@ export class UpdateStockDTO {
|
|||
|
||||
@ApiProperty()
|
||||
@Rule(RuleType.string())
|
||||
productSku: string;
|
||||
sku: string;
|
||||
|
||||
@ApiProperty()
|
||||
@Rule(RuleType.number())
|
||||
|
|
|
|||
|
|
@ -23,6 +23,11 @@ export class Product {
|
|||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
// 类型 主要用来区分混装和单品 单品死
|
||||
@ApiProperty({ description: '类型' })
|
||||
@Column({ length: 16, default: 'single' })
|
||||
type: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'ZYN 6MG WINTERGREEN',
|
||||
description: '产品名称',
|
||||
|
|
@ -49,10 +54,7 @@ export class Product {
|
|||
@ApiProperty({ description: '价格', example: 99.99 })
|
||||
@Column({ type: 'decimal', precision: 10, scale: 2, default: 0 })
|
||||
price: number;
|
||||
// 类型 主要用来区分混装和单品 单品死
|
||||
@ApiProperty({ description: '类型' })
|
||||
@Column({ length: 16, default: 'simple' })
|
||||
type: string;
|
||||
|
||||
// 促销价格
|
||||
@ApiProperty({ description: '促销价格', example: 99.99 })
|
||||
@Column({ type: 'decimal', precision: 10, scale: 2, default: 0 })
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export class ProductStockComponent {
|
|||
|
||||
@ApiProperty({ description: '组件所关联的 SKU', type: 'string' })
|
||||
@Column({ type: 'varchar', length: 64 })
|
||||
productSku: string;
|
||||
sku: string;
|
||||
|
||||
@ApiProperty({ type: Number, description: '组成数量' })
|
||||
@Column({ type: 'int', default: 1 })
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export class PurchaseOrderItem {
|
|||
|
||||
@ApiProperty({ type: String })
|
||||
@Column()
|
||||
productSku: string;
|
||||
sku: string;
|
||||
|
||||
@ApiProperty({ type: String })
|
||||
@Column()
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export class Stock {
|
|||
|
||||
@ApiProperty({ type: String })
|
||||
@Column()
|
||||
productSku: string;
|
||||
sku: string;
|
||||
|
||||
@ApiProperty({ type: Number })
|
||||
@Column()
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export class StockRecord {
|
|||
|
||||
@ApiProperty({ type: String })
|
||||
@Column()
|
||||
productSku: string;
|
||||
sku: string;
|
||||
|
||||
@ApiProperty({ type: StockRecordOperationType })
|
||||
@Column({ type: 'enum', enum: StockRecordOperationType })
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export class TransferItem {
|
|||
|
||||
@ApiProperty({ type: String })
|
||||
@Column()
|
||||
productSku: string;
|
||||
sku: string;
|
||||
|
||||
@ApiProperty({ type: String })
|
||||
@Column()
|
||||
|
|
|
|||
|
|
@ -527,14 +527,14 @@ export class LogisticsService {
|
|||
const stock = await stockRepo.findOne({
|
||||
where: {
|
||||
stockPointId: orderShipments[0].stockPointId,
|
||||
productSku: item.sku,
|
||||
sku: item.sku,
|
||||
},
|
||||
});
|
||||
stock.quantity += item.quantity;
|
||||
await stockRepo.save(stock);
|
||||
await stockRecordRepo.save({
|
||||
stockPointId: orderShipments[0].stockPointId,
|
||||
productSku: item.sku,
|
||||
sku: item.sku,
|
||||
operationType: StockRecordOperationType.IN,
|
||||
quantityChange: item.quantity,
|
||||
operatorId: userId,
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ export class OrderService {
|
|||
for (const item of items) {
|
||||
const updateStock = new UpdateStockDTO();
|
||||
updateStock.stockPointId = stockPointId;
|
||||
updateStock.productSku = item.sku;
|
||||
updateStock.sku = item.sku;
|
||||
updateStock.quantityChange = item.quantity;
|
||||
updateStock.operationType = StockRecordOperationType.OUT;
|
||||
updateStock.operatorId = 1;
|
||||
|
|
|
|||
|
|
@ -177,17 +177,19 @@ export class ProductService {
|
|||
const [items, total] = await qb.getManyAndCount();
|
||||
|
||||
// 中文注释:根据类型填充组成信息
|
||||
for (const p of items) {
|
||||
if (p.type === 'simple') {
|
||||
for (const product of items) {
|
||||
if (product.type === 'single') {
|
||||
// 中文注释:单品不持久化组成,这里仅返回一个基于 SKU 的虚拟组成
|
||||
const comp = new ProductStockComponent();
|
||||
comp.productId = p.id;
|
||||
comp.productSku = p.sku;
|
||||
comp.quantity = 1;
|
||||
p.components = [comp];
|
||||
const component = new ProductStockComponent();
|
||||
component.productId = product.id;
|
||||
component.sku = product.sku;
|
||||
component.quantity = 1;
|
||||
product.components = [component];
|
||||
} else {
|
||||
// 中文注释:混装商品返回持久化的 SKU 组成
|
||||
p.components = await this.productStockComponentModel.find({ where: { productId: p.id } });
|
||||
product.components = await this.productStockComponentModel.find({
|
||||
where: { productId: product.id },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -273,7 +275,7 @@ export class ProductService {
|
|||
product.description = description;
|
||||
product.attributes = resolvedAttributes;
|
||||
// 条件判断(中文注释:设置商品类型,默认 simple)
|
||||
product.type = (createProductDTO.type as any) || 'simple';
|
||||
product.type = (createProductDTO.type as any) || 'single';
|
||||
|
||||
// 生成或设置 SKU(中文注释:基于属性字典项的 name 生成)
|
||||
if (sku) {
|
||||
|
|
@ -387,10 +389,10 @@ export class ProductService {
|
|||
|
||||
let components: ProductStockComponent[] = [];
|
||||
// 条件判断(中文注释:单品 simple 不持久化组成,按 SKU 动态返回单条组成)
|
||||
if (product.type === 'simple') {
|
||||
if (product.type === 'single') {
|
||||
const comp = new ProductStockComponent();
|
||||
comp.productId = productId;
|
||||
comp.productSku = product.sku;
|
||||
comp.sku = product.sku;
|
||||
comp.quantity = 1;
|
||||
components = [comp];
|
||||
} else {
|
||||
|
|
@ -399,14 +401,14 @@ export class ProductService {
|
|||
}
|
||||
|
||||
// 中文注释:获取所有组件的 SKU 列表
|
||||
const skus = components.map(c => c.productSku);
|
||||
const skus = components.map(c => c.sku);
|
||||
if (skus.length === 0) {
|
||||
return components;
|
||||
}
|
||||
|
||||
// 中文注释:查询这些 SKU 的库存信息
|
||||
const stocks = await this.stockModel.find({
|
||||
where: { productSku: In(skus) },
|
||||
where: { sku: In(skus) },
|
||||
});
|
||||
|
||||
// 中文注释:获取所有相关的库存点 ID
|
||||
|
|
@ -419,12 +421,12 @@ export class ProductService {
|
|||
|
||||
// 中文注释:将库存信息按 SKU 分组
|
||||
const stockMap = stocks.reduce((map, stock) => {
|
||||
if (!map[stock.productSku]) {
|
||||
map[stock.productSku] = [];
|
||||
if (!map[stock.sku]) {
|
||||
map[stock.sku] = [];
|
||||
}
|
||||
const stockPoint = stockPointMap[stock.stockPointId];
|
||||
if (stockPoint) {
|
||||
map[stock.productSku].push({
|
||||
map[stock.sku].push({
|
||||
name: stockPoint.name,
|
||||
quantity: stock.quantity,
|
||||
});
|
||||
|
|
@ -436,7 +438,7 @@ export class ProductService {
|
|||
const componentsWithStock = components.map(comp => {
|
||||
return {
|
||||
...comp,
|
||||
stock: stockMap[comp.productSku] || [],
|
||||
stock: stockMap[comp.sku] || [],
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -452,7 +454,7 @@ export class ProductService {
|
|||
const product = await this.productModel.findOne({ where: { id: productId } });
|
||||
if (!product) throw new Error(`产品 ID ${productId} 不存在`);
|
||||
// 条件判断(中文注释:单品 simple 不允许手动设置组成)
|
||||
if (product.type === 'simple') {
|
||||
if (product.type === 'single') {
|
||||
throw new Error('单品无需设置组成');
|
||||
}
|
||||
|
||||
|
|
@ -472,7 +474,7 @@ export class ProductService {
|
|||
}
|
||||
const comp = new ProductStockComponent();
|
||||
comp.productId = productId;
|
||||
comp.productSku = i.sku;
|
||||
comp.sku = i.sku;
|
||||
comp.quantity = i.quantity;
|
||||
created.push(await this.productStockComponentModel.save(comp));
|
||||
}
|
||||
|
|
@ -486,19 +488,19 @@ export class ProductService {
|
|||
if (!product) throw new Error(`产品 ID ${productId} 不存在`);
|
||||
// 中文注释:按 SKU 自动绑定
|
||||
// 条件判断:simple 类型不持久化组成,直接返回单条基于 SKU 的组成
|
||||
if (product.type === 'simple') {
|
||||
if (product.type === 'single') {
|
||||
const comp = new ProductStockComponent();
|
||||
comp.productId = productId;
|
||||
comp.productSku = product.sku;
|
||||
comp.sku = product.sku;
|
||||
comp.quantity = 1; // 默认数量 1
|
||||
return [comp];
|
||||
}
|
||||
// bundle 类型:若不存在则持久化一条基于 SKU 的组成
|
||||
const exist = await this.productStockComponentModel.findOne({ where: { productId, productSku: product.sku } });
|
||||
const exist = await this.productStockComponentModel.findOne({ where: { productId, sku: product.sku } });
|
||||
if (!exist) {
|
||||
const comp = new ProductStockComponent();
|
||||
comp.productId = productId;
|
||||
comp.productSku = product.sku;
|
||||
comp.sku = product.sku;
|
||||
comp.quantity = 1;
|
||||
await this.productStockComponentModel.save(comp);
|
||||
}
|
||||
|
|
@ -525,13 +527,13 @@ export class ProductService {
|
|||
if (!product) {
|
||||
throw new Error(`产品 ID ${id} 不存在`);
|
||||
}
|
||||
const productSku = product.sku;
|
||||
const sku = product.sku;
|
||||
|
||||
// 查询 wp_product 表中是否存在与该 SKU 关联的产品
|
||||
const wpProduct = await this.wpProductModel
|
||||
.createQueryBuilder('wp_product')
|
||||
.where('JSON_CONTAINS(wp_product.constitution, :sku)', {
|
||||
sku: JSON.stringify({ sku: productSku }),
|
||||
sku: JSON.stringify({ sku: sku }),
|
||||
})
|
||||
.getOne();
|
||||
if (wpProduct) {
|
||||
|
|
@ -541,7 +543,7 @@ export class ProductService {
|
|||
const variation = await this.variationModel
|
||||
.createQueryBuilder('variation')
|
||||
.where('JSON_CONTAINS(variation.constitution, :sku)', {
|
||||
sku: JSON.stringify({ sku: productSku }),
|
||||
sku: JSON.stringify({ sku: sku }),
|
||||
})
|
||||
.getOne();
|
||||
|
||||
|
|
|
|||
|
|
@ -656,10 +656,10 @@ export class StatisticsService {
|
|||
const offset = (current - 1) * pageSize;
|
||||
const countSql = `
|
||||
WITH product_list AS (
|
||||
SELECT DISTINCT s.productSku
|
||||
SELECT DISTINCT s.sku
|
||||
FROM stock s
|
||||
LEFT JOIN stock_point sp ON s.stockPointId = sp.id
|
||||
LEFT JOIN product p ON s.productSku = p.sku
|
||||
LEFT JOIN product p ON s.sku = p.sku
|
||||
WHERE sp.ignore = FALSE
|
||||
${countnameFilter}
|
||||
)
|
||||
|
|
@ -674,27 +674,27 @@ export class StatisticsService {
|
|||
const sql = `
|
||||
WITH stock_summary AS (
|
||||
SELECT
|
||||
s.productSku,
|
||||
s.sku,
|
||||
JSON_ARRAYAGG(JSON_OBJECT('id', sp.id, 'quantity', s.quantity)) AS stockDetails,
|
||||
SUM(s.quantity) AS totalStock,
|
||||
SUM(CASE WHEN sp.inCanada THEN s.quantity ELSE 0 END) AS caTotalStock
|
||||
FROM stock s
|
||||
JOIN stock_point sp ON s.stockPointId = sp.id
|
||||
WHERE sp.ignore = FALSE
|
||||
GROUP BY s.productSku
|
||||
GROUP BY s.sku
|
||||
),
|
||||
transfer_stock AS (
|
||||
SELECT
|
||||
ti.productSku,
|
||||
ti.sku,
|
||||
SUM(ti.quantity) AS transitStock
|
||||
FROM transfer_item ti
|
||||
JOIN transfer t ON ti.transferId = t.id
|
||||
WHERE t.isCancel = FALSE AND t.isArrived = FALSE
|
||||
GROUP BY ti.productSku
|
||||
GROUP BY ti.sku
|
||||
),
|
||||
30_sales_summary AS (
|
||||
SELECT
|
||||
os.sku AS productSku,
|
||||
os.sku AS sku,
|
||||
SUM(os.quantity) AS totalSales
|
||||
FROM order_sale os
|
||||
JOIN \`order\` o ON os.orderId = o.id
|
||||
|
|
@ -704,7 +704,7 @@ export class StatisticsService {
|
|||
),
|
||||
15_sales_summary AS (
|
||||
SELECT
|
||||
os.sku AS productSku,
|
||||
os.sku AS sku,
|
||||
2 * SUM(os.quantity) AS totalSales
|
||||
FROM order_sale os
|
||||
JOIN \`order\` o ON os.orderId = o.id
|
||||
|
|
@ -714,36 +714,36 @@ export class StatisticsService {
|
|||
),
|
||||
sales_max_summary AS (
|
||||
SELECT
|
||||
s30.productSku AS productSku,
|
||||
s30.sku AS sku,
|
||||
COALESCE(s30.totalSales, 0) AS totalSales_30,
|
||||
COALESCE(s15.totalSales, 0) AS totalSales_15,
|
||||
GREATEST(COALESCE(s30.totalSales, 0), COALESCE(s15.totalSales, 0)) AS maxSales
|
||||
FROM 30_sales_summary s30
|
||||
LEFT JOIN 15_sales_summary s15
|
||||
ON s30.productSku = s15.productSku
|
||||
ON s30.sku = s15.sku
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
s15.productSku AS productSku,
|
||||
s15.sku AS sku,
|
||||
0 AS totalSales_30,
|
||||
COALESCE(s15.totalSales, 0) AS totalSales_15,
|
||||
COALESCE(s15.totalSales, 0) AS maxSales
|
||||
FROM 15_sales_summary s15
|
||||
LEFT JOIN 30_sales_summary s30
|
||||
ON s30.productSku = s15.productSku
|
||||
WHERE s30.productSku IS NULL
|
||||
ON s30.sku = s15.sku
|
||||
WHERE s30.sku IS NULL
|
||||
),
|
||||
product_name_summary AS (
|
||||
SELECT
|
||||
p.sku AS productSku,
|
||||
p.sku AS sku,
|
||||
COALESCE(MAX(os.name), MAX(p.name)) AS productName
|
||||
FROM product p
|
||||
LEFT JOIN order_sale os ON p.sku = os.sku
|
||||
GROUP BY p.sku
|
||||
)
|
||||
SELECT
|
||||
ss.productSku,
|
||||
ss.sku,
|
||||
ss.stockDetails,
|
||||
COALESCE(ts.transitStock, 0) AS transitStock,
|
||||
(COALESCE(ss.totalStock, 0) + COALESCE(ts.transitStock, 0)) AS totalStock,
|
||||
|
|
@ -761,9 +761,9 @@ export class StatisticsService {
|
|||
sales.maxSales * 4 AS restockQuantity,
|
||||
pns.productName
|
||||
FROM stock_summary ss
|
||||
LEFT JOIN transfer_stock ts ON ss.productSku = ts.productSku
|
||||
LEFT JOIN sales_max_summary sales ON ss.productSku = sales.productSku
|
||||
LEFT JOIN product_name_summary pns ON ss.productSku = pns.productSku
|
||||
LEFT JOIN transfer_stock ts ON ss.sku = ts.sku
|
||||
LEFT JOIN sales_max_summary sales ON ss.sku = sales.sku
|
||||
LEFT JOIN product_name_summary pns ON ss.sku = pns.sku
|
||||
WHERE 1 = 1
|
||||
${nameFilter}
|
||||
ORDER BY caAvailableDays
|
||||
|
|
@ -791,10 +791,10 @@ export class StatisticsService {
|
|||
const offset = (current - 1) * pageSize;
|
||||
const countSql = `
|
||||
WITH product_list AS (
|
||||
SELECT DISTINCT s.productSku
|
||||
SELECT DISTINCT s.sku
|
||||
FROM stock s
|
||||
LEFT JOIN stock_point sp ON s.stockPointId = sp.id
|
||||
LEFT JOIN product p ON s.productSku = p.sku
|
||||
LEFT JOIN product p ON s.sku = p.sku
|
||||
WHERE sp.ignore = FALSE
|
||||
${countnameFilter}
|
||||
)
|
||||
|
|
@ -810,36 +810,36 @@ export class StatisticsService {
|
|||
const sql = `
|
||||
WITH stock_summary AS (
|
||||
SELECT
|
||||
s.productSku,
|
||||
s.sku,
|
||||
SUM(s.quantity) AS totalStock
|
||||
FROM stock s
|
||||
JOIN stock_point sp ON s.stockPointId = sp.id
|
||||
WHERE sp.ignore = FALSE
|
||||
GROUP BY s.productSku
|
||||
GROUP BY s.sku
|
||||
),
|
||||
transfer_stock AS (
|
||||
SELECT
|
||||
ti.productSku,
|
||||
ti.sku,
|
||||
SUM(ti.quantity) AS transitStock
|
||||
FROM transfer_item ti
|
||||
JOIN transfer t ON ti.transferId = t.id
|
||||
WHERE t.isCancel = FALSE AND t.isArrived = FALSE
|
||||
GROUP BY ti.productSku
|
||||
GROUP BY ti.sku
|
||||
),
|
||||
b_sales_data_raw As (
|
||||
SELECT
|
||||
sr.productSku,
|
||||
sr.sku,
|
||||
DATE_FORMAT(sr.createdAt, '%Y-%m') AS month,
|
||||
SUM(sr.quantityChange) AS sales
|
||||
FROM stock_record sr
|
||||
JOIN stock_point sp ON sr.stockPointId = sp.id
|
||||
WHERE sp.isB
|
||||
AND sr.createdAt >= DATE_FORMAT(NOW() - INTERVAL 2 MONTH, '%Y-%m-01')
|
||||
GROUP BY sr.productSku, month
|
||||
GROUP BY sr.sku, month
|
||||
),
|
||||
sales_data_raw AS (
|
||||
SELECT
|
||||
os.sku AS productSku,
|
||||
os.sku AS sku,
|
||||
DATE_FORMAT(o.date_paid, '%Y-%m') AS month,
|
||||
SUM(CASE WHEN DAY(o.date_paid) <= 10 THEN os.quantity ELSE 0 END) AS early_sales,
|
||||
SUM(CASE WHEN DAY(o.date_paid) > 10 AND DAY(o.date_paid) <= 20 THEN os.quantity ELSE 0 END) AS mid_sales,
|
||||
|
|
@ -852,7 +852,7 @@ export class StatisticsService {
|
|||
),
|
||||
monthly_sales_summary AS (
|
||||
SELECT
|
||||
sdr.productSku,
|
||||
sdr.sku,
|
||||
JSON_ARRAYAGG(
|
||||
JSON_OBJECT(
|
||||
'month', sdr.month,
|
||||
|
|
@ -863,12 +863,12 @@ export class StatisticsService {
|
|||
)
|
||||
) AS sales_data
|
||||
FROM sales_data_raw sdr
|
||||
LEFT JOIN b_sales_data_raw b ON sdr.productSku = b.productSku AND sdr.month = b.month
|
||||
GROUP BY sdr.productSku
|
||||
LEFT JOIN b_sales_data_raw b ON sdr.sku = b.sku AND sdr.month = b.month
|
||||
GROUP BY sdr.sku
|
||||
),
|
||||
sales_summary AS (
|
||||
SELECT
|
||||
os.sku AS productSku,
|
||||
os.sku AS sku,
|
||||
SUM(CASE WHEN o.date_paid >= CURDATE() - INTERVAL 30 DAY THEN os.quantity ELSE 0 END) AS last_30_days_sales,
|
||||
SUM(CASE WHEN o.date_paid >= CURDATE() - INTERVAL 15 DAY THEN os.quantity ELSE 0 END) AS last_15_days_sales,
|
||||
SUM(CASE WHEN DATE_FORMAT(o.date_paid, '%Y-%m') = DATE_FORMAT(CURDATE() - INTERVAL 1 MONTH, '%Y-%m') THEN os.quantity ELSE 0 END) AS last_month_sales
|
||||
|
|
@ -880,14 +880,14 @@ export class StatisticsService {
|
|||
),
|
||||
product_name_summary AS (
|
||||
SELECT
|
||||
p.sku AS productSku,
|
||||
p.sku AS sku,
|
||||
COALESCE(MAX(os.name), MAX(p.name)) AS productName
|
||||
FROM product p
|
||||
LEFT JOIN order_sale os ON p.sku = os.sku
|
||||
GROUP BY p.sku
|
||||
)
|
||||
SELECT
|
||||
ss.productSku,
|
||||
ss.sku,
|
||||
(COALESCE(ss.totalStock, 0) + COALESCE(ts.transitStock, 0)) AS totalStock,
|
||||
ms.sales_data AS monthlySalesData,
|
||||
pns.productName,
|
||||
|
|
@ -900,10 +900,10 @@ export class StatisticsService {
|
|||
ELSE NULL
|
||||
END AS stock_ratio
|
||||
FROM stock_summary ss
|
||||
LEFT JOIN transfer_stock ts ON ss.productSku = ts.productSku
|
||||
LEFT JOIN monthly_sales_summary ms ON ss.productSku = ms.productSku
|
||||
LEFT JOIN product_name_summary pns ON ss.productSku = pns.productSku
|
||||
LEFT JOIN sales_summary ssum ON ss.productSku = ssum.productSku
|
||||
LEFT JOIN transfer_stock ts ON ss.sku = ts.sku
|
||||
LEFT JOIN monthly_sales_summary ms ON ss.sku = ms.sku
|
||||
LEFT JOIN product_name_summary pns ON ss.sku = pns.sku
|
||||
LEFT JOIN sales_summary ssum ON ss.sku = ssum.sku
|
||||
WHERE 1 = 1
|
||||
${nameFilter}
|
||||
ORDER BY
|
||||
|
|
|
|||
|
|
@ -167,7 +167,7 @@ export class StockService {
|
|||
qb
|
||||
.select([
|
||||
'poi.purchaseOrderId AS purchaseOrderId',
|
||||
"JSON_ARRAYAGG(JSON_OBJECT('id', poi.id, 'productName', poi.productName,'productSku', poi.productSku, 'quantity', poi.quantity, 'price', poi.price)) AS items",
|
||||
"JSON_ARRAYAGG(JSON_OBJECT('id', poi.id, 'productName', poi.productName,'sku', poi.sku, 'quantity', poi.quantity, 'price', poi.price)) AS items",
|
||||
])
|
||||
.from(PurchaseOrderItem, 'poi')
|
||||
.groupBy('poi.purchaseOrderId'),
|
||||
|
|
@ -191,7 +191,7 @@ export class StockService {
|
|||
async hasStockBySku(sku: string): Promise<boolean> {
|
||||
const count = await this.stockModel
|
||||
.createQueryBuilder('stock')
|
||||
.where('stock.productSku = :sku', { sku })
|
||||
.where('stock.sku = :sku', { sku })
|
||||
.andWhere('stock.quantity > 0')
|
||||
.getCount();
|
||||
return count > 0;
|
||||
|
|
@ -217,7 +217,7 @@ export class StockService {
|
|||
for (const item of items) {
|
||||
const updateStock = new UpdateStockDTO();
|
||||
updateStock.stockPointId = purchaseOrder.stockPointId;
|
||||
updateStock.productSku = item.productSku;
|
||||
updateStock.sku = item.sku;
|
||||
updateStock.quantityChange = item.quantity;
|
||||
updateStock.operationType = StockRecordOperationType.IN;
|
||||
updateStock.operatorId = userId;
|
||||
|
|
@ -240,7 +240,7 @@ export class StockService {
|
|||
|
||||
// 获取库存列表
|
||||
async getStocks(query: QueryStockDTO) {
|
||||
const { current = 1, pageSize = 10, productName, productSku } = query;
|
||||
const { current = 1, pageSize = 10, productName, sku } = query;
|
||||
const nameKeywords = productName
|
||||
? productName.split(' ').filter(Boolean)
|
||||
: [];
|
||||
|
|
@ -249,31 +249,31 @@ export class StockService {
|
|||
.createQueryBuilder('stock')
|
||||
.select([
|
||||
// 'stock.id as id',
|
||||
'stock.productSku as productSku',
|
||||
'stock.sku as sku',
|
||||
'product.name as productName',
|
||||
'product.nameCn as productNameCn',
|
||||
'JSON_ARRAYAGG(JSON_OBJECT("id", stock.stockPointId, "quantity", stock.quantity)) as stockPoint',
|
||||
'MIN(stock.updatedAt) as updatedAt',
|
||||
'MAX(stock.createdAt) as createdAt',
|
||||
])
|
||||
.leftJoin(Product, 'product', 'product.sku = stock.productSku')
|
||||
.groupBy('stock.productSku')
|
||||
.leftJoin(Product, 'product', 'product.sku = stock.sku')
|
||||
.groupBy('stock.sku')
|
||||
.addGroupBy('product.name')
|
||||
.addGroupBy('product.nameCn');
|
||||
let totalQueryBuilder = this.stockModel
|
||||
.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 });
|
||||
.select('COUNT(DISTINCT stock.sku)', 'count')
|
||||
.leftJoin(Product, 'product', 'product.sku = stock.sku');
|
||||
if (sku) {
|
||||
queryBuilder.andWhere('stock.sku = :sku', { sku });
|
||||
totalQueryBuilder.andWhere('stock.sku = :sku', { sku });
|
||||
}
|
||||
if (nameKeywords.length) {
|
||||
nameKeywords.forEach((name, index) => {
|
||||
queryBuilder.andWhere(
|
||||
`EXISTS (
|
||||
SELECT 1 FROM product p
|
||||
WHERE p.sku = stock.productSku
|
||||
WHERE p.sku = stock.sku
|
||||
AND p.name LIKE :name${index}
|
||||
)`,
|
||||
{ [`name${index}`]: `%${name}%` }
|
||||
|
|
@ -281,7 +281,7 @@ export class StockService {
|
|||
totalQueryBuilder.andWhere(
|
||||
`EXISTS (
|
||||
SELECT 1 FROM product p
|
||||
WHERE p.sku = stock.productSku
|
||||
WHERE p.sku = stock.sku
|
||||
AND p.name LIKE :name${index}
|
||||
)`,
|
||||
{ [`name${index}`]: `%${name}%` }
|
||||
|
|
@ -291,7 +291,7 @@ export class StockService {
|
|||
if (query.order) {
|
||||
const sortFieldMap: Record<string, string> = {
|
||||
productName: 'product.name',
|
||||
productSku: 'stock.productSku',
|
||||
sku: 'stock.sku',
|
||||
updatedAt: 'updatedAt',
|
||||
createdAt: 'createdAt',
|
||||
};
|
||||
|
|
@ -335,15 +335,15 @@ export class StockService {
|
|||
|
||||
const transfer = await this.transferModel
|
||||
.createQueryBuilder('t')
|
||||
.select(['ti.productSku as productSku', 'SUM(ti.quantity) as quantity'])
|
||||
.select(['ti.sku as sku', 'SUM(ti.quantity) as quantity'])
|
||||
.leftJoin(TransferItem, 'ti', 'ti.transferId = t.id')
|
||||
.where('!t.isArrived and !t.isCancel and !t.isLost')
|
||||
.groupBy('ti.productSku')
|
||||
.groupBy('ti.sku')
|
||||
.getRawMany();
|
||||
|
||||
for (const item of items) {
|
||||
item.inTransitQuantity =
|
||||
transfer.find(t => t.productSku === item.productSku)?.quantity || 0;
|
||||
transfer.find(t => t.sku === item.sku)?.quantity || 0;
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -361,10 +361,10 @@ export class StockService {
|
|||
|
||||
const stocks = await this.stockModel
|
||||
.createQueryBuilder('stock')
|
||||
.select('stock.productSku', 'productSku')
|
||||
.select('stock.sku', 'sku')
|
||||
.addSelect('SUM(stock.quantity)', 'totalQuantity')
|
||||
.where('stock.productSku IN (:...skus)', { skus })
|
||||
.groupBy('stock.productSku')
|
||||
.where('stock.sku IN (:...skus)', { skus })
|
||||
.groupBy('stock.sku')
|
||||
.getRawMany();
|
||||
|
||||
return stocks;
|
||||
|
|
@ -374,7 +374,7 @@ export class StockService {
|
|||
async updateStock(data: UpdateStockDTO) {
|
||||
const {
|
||||
stockPointId,
|
||||
productSku,
|
||||
sku,
|
||||
quantityChange,
|
||||
operationType,
|
||||
operatorId,
|
||||
|
|
@ -383,13 +383,13 @@ export class StockService {
|
|||
|
||||
const stock = await this.stockModel.findOneBy({
|
||||
stockPointId,
|
||||
productSku,
|
||||
sku,
|
||||
});
|
||||
if (!stock) {
|
||||
// 如果库存不存在,则直接新增
|
||||
const newStock = this.stockModel.create({
|
||||
stockPointId,
|
||||
productSku,
|
||||
sku,
|
||||
quantity: operationType === 'in' ? quantityChange : -quantityChange,
|
||||
});
|
||||
await this.stockModel.save(newStock);
|
||||
|
|
@ -406,7 +406,7 @@ export class StockService {
|
|||
// 记录库存变更日志
|
||||
const stockRecord = this.stockRecordModel.create({
|
||||
stockPointId,
|
||||
productSku,
|
||||
sku,
|
||||
operationType,
|
||||
quantityChange,
|
||||
operatorId,
|
||||
|
|
@ -421,7 +421,7 @@ export class StockService {
|
|||
current = 1,
|
||||
pageSize = 10,
|
||||
stockPointId,
|
||||
productSku,
|
||||
sku,
|
||||
productName,
|
||||
operationType,
|
||||
startDate,
|
||||
|
|
@ -430,14 +430,14 @@ export class StockService {
|
|||
|
||||
const where: any = {};
|
||||
if (stockPointId) where.stockPointId = stockPointId;
|
||||
if (productSku) where.productSku = productSku;
|
||||
if (sku) where.sku = sku;
|
||||
if (operationType) where.operationType = operationType;
|
||||
if (startDate) where.createdAt = MoreThan(startDate);
|
||||
if (endDate) where.createdAt = LessThan(endDate);
|
||||
if (startDate && endDate) where.createdAt = Between(startDate, endDate);
|
||||
const queryBuilder = this.stockRecordModel
|
||||
.createQueryBuilder('stock_record')
|
||||
.leftJoin(Product, 'product', 'product.sku = stock_record.productSku')
|
||||
.leftJoin(Product, 'product', 'product.sku = stock_record.sku')
|
||||
.leftJoin(User, 'user', 'stock_record.operatorId = user.id')
|
||||
.leftJoin(StockPoint, 'sp', 'sp.id = stock_record.stockPointId')
|
||||
.select([
|
||||
|
|
@ -470,7 +470,7 @@ export class StockService {
|
|||
// for (const item of items) {
|
||||
// const stock = await this.stockModel.findOneBy({
|
||||
// stockPointId: sourceStockPointId,
|
||||
// productSku: item.productSku,
|
||||
// sku: item.sku,
|
||||
// });
|
||||
// if (!stock || stock.quantity < item.quantity)
|
||||
// throw new Error(`${item.productName} 库存不足`);
|
||||
|
|
@ -496,7 +496,7 @@ export class StockService {
|
|||
item.transferId = transfer.id;
|
||||
const updateStock = new UpdateStockDTO();
|
||||
updateStock.stockPointId = sourceStockPointId;
|
||||
updateStock.productSku = item.productSku;
|
||||
updateStock.sku = item.sku;
|
||||
updateStock.quantityChange = item.quantity;
|
||||
updateStock.operationType = StockRecordOperationType.OUT;
|
||||
updateStock.operatorId = userId;
|
||||
|
|
@ -530,7 +530,7 @@ export class StockService {
|
|||
qb
|
||||
.select([
|
||||
'ti.transferId AS transferId',
|
||||
"JSON_ARRAYAGG(JSON_OBJECT('id', ti.id, 'productName', ti.productName,'productSku', ti.productSku, 'quantity', ti.quantity)) AS items",
|
||||
"JSON_ARRAYAGG(JSON_OBJECT('id', ti.id, 'productName', ti.productName,'sku', ti.sku, 'quantity', ti.quantity)) AS items",
|
||||
])
|
||||
.from(TransferItem, 'ti')
|
||||
.groupBy('ti.transferId'),
|
||||
|
|
@ -561,7 +561,7 @@ export class StockService {
|
|||
for (const item of items) {
|
||||
const updateStock = new UpdateStockDTO();
|
||||
updateStock.stockPointId = transfer.sourceStockPointId;
|
||||
updateStock.productSku = item.productSku;
|
||||
updateStock.sku = item.sku;
|
||||
updateStock.quantityChange = item.quantity;
|
||||
updateStock.operationType = StockRecordOperationType.IN;
|
||||
updateStock.operatorId = userId;
|
||||
|
|
@ -584,7 +584,7 @@ export class StockService {
|
|||
for (const item of items) {
|
||||
const updateStock = new UpdateStockDTO();
|
||||
updateStock.stockPointId = transfer.destStockPointId;
|
||||
updateStock.productSku = item.productSku;
|
||||
updateStock.sku = item.sku;
|
||||
updateStock.quantityChange = item.quantity;
|
||||
updateStock.operationType = StockRecordOperationType.IN;
|
||||
updateStock.operatorId = userId;
|
||||
|
|
@ -619,7 +619,7 @@ export class StockService {
|
|||
item.transferId = transfer.id;
|
||||
const updateStock = new UpdateStockDTO();
|
||||
updateStock.stockPointId = sourceStockPointId;
|
||||
updateStock.productSku = item.productSku;
|
||||
updateStock.sku = item.sku;
|
||||
updateStock.quantityChange = item.quantity;
|
||||
updateStock.operationType = StockRecordOperationType.IN;
|
||||
updateStock.operatorId = userId;
|
||||
|
|
@ -632,7 +632,7 @@ export class StockService {
|
|||
item.transferId = transfer.id;
|
||||
const updateStock = new UpdateStockDTO();
|
||||
updateStock.stockPointId = sourceStockPointId;
|
||||
updateStock.productSku = item.productSku;
|
||||
updateStock.sku = item.sku;
|
||||
updateStock.quantityChange = item.quantity;
|
||||
updateStock.operationType = StockRecordOperationType.OUT;
|
||||
updateStock.operatorId = userId;
|
||||
|
|
|
|||
Loading…
Reference in New Issue