feat(area): 重构区域模块,使用i18n-iso-countries管理国家数据
refactor: 统一将productName字段重命名为name chore: 添加i18n-iso-countries依赖 style: 优化字典名称格式化逻辑
This commit is contained in:
parent
10b42eca7a
commit
f20f4727f6
|
|
@ -27,6 +27,7 @@
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"csv-parse": "^6.1.0",
|
"csv-parse": "^6.1.0",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
|
"i18n-iso-countries": "^7.14.0",
|
||||||
"mysql2": "^3.15.3",
|
"mysql2": "^3.15.3",
|
||||||
"nodemailer": "^7.0.5",
|
"nodemailer": "^7.0.5",
|
||||||
"npm-check-updates": "^19.1.2",
|
"npm-check-updates": "^19.1.2",
|
||||||
|
|
@ -2044,6 +2045,12 @@
|
||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/diacritics": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/diacritics/-/diacritics-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-wlwEkqcsaxvPJML+rDh/2iS824jbREk6DUMUKkEaSlxdYHeS43cClJtsWglvw2RfeXGm6ohKDqsXteJ5sP5enA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/dir-glob": {
|
"node_modules/dir-glob": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/dir-glob/-/dir-glob-3.0.1.tgz",
|
||||||
|
|
@ -2691,6 +2698,18 @@
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/i18n-iso-countries": {
|
||||||
|
"version": "7.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18n-iso-countries/-/i18n-iso-countries-7.14.0.tgz",
|
||||||
|
"integrity": "sha512-nXHJZYtNrfsi1UQbyRqm3Gou431elgLjKl//CYlnBGt5aTWdRPH1PiS2T/p/n8Q8LnqYqzQJik3Q7mkwvLokeg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"diacritics": "1.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/iconv-lite": {
|
"node_modules/iconv-lite": {
|
||||||
"version": "0.7.0",
|
"version": "0.7.0",
|
||||||
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.0.tgz",
|
"resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"csv-parse": "^6.1.0",
|
"csv-parse": "^6.1.0",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
|
"i18n-iso-countries": "^7.14.0",
|
||||||
"mysql2": "^3.15.3",
|
"mysql2": "^3.15.3",
|
||||||
"nodemailer": "^7.0.5",
|
"nodemailer": "^7.0.5",
|
||||||
"npm-check-updates": "^19.1.2",
|
"npm-check-updates": "^19.1.2",
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,5 @@
|
||||||
|
|
||||||
import { Inject } from '@midwayjs/core';
|
import { Body, Context, Controller, Del, Get, Inject, Param, Post, Put, Query } from '@midwayjs/core';
|
||||||
import {
|
|
||||||
Body,
|
|
||||||
Controller,
|
|
||||||
Del,
|
|
||||||
Get,
|
|
||||||
Param,
|
|
||||||
Post,
|
|
||||||
Put,
|
|
||||||
Query,
|
|
||||||
} from '@midwayjs/decorator';
|
|
||||||
import {
|
import {
|
||||||
ApiBearerAuth,
|
ApiBearerAuth,
|
||||||
ApiBody,
|
ApiBody,
|
||||||
|
|
@ -22,14 +12,39 @@ import { AreaService } from '../service/area.service';
|
||||||
import { CreateAreaDTO, QueryAreaDTO, UpdateAreaDTO } from '../dto/area.dto';
|
import { CreateAreaDTO, QueryAreaDTO, UpdateAreaDTO } from '../dto/area.dto';
|
||||||
import { errorResponse, successResponse } from '../utils/response.util';
|
import { errorResponse, successResponse } from '../utils/response.util';
|
||||||
import { Area } from '../entity/area.entity';
|
import { Area } from '../entity/area.entity';
|
||||||
|
import * as countries from 'i18n-iso-countries';
|
||||||
|
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@ApiTags('Area')
|
@ApiTags('Area')
|
||||||
@Controller('/api/area')
|
@Controller('/area')
|
||||||
export class AreaController {
|
export class AreaController {
|
||||||
|
@Inject()
|
||||||
|
ctx: Context;
|
||||||
|
|
||||||
@Inject()
|
@Inject()
|
||||||
areaService: AreaService;
|
areaService: AreaService;
|
||||||
|
|
||||||
|
@ApiOperation({ summary: '获取国家列表' })
|
||||||
|
@ApiOkResponse({ description: '国家列表' })
|
||||||
|
@Get('/countries')
|
||||||
|
async getCountries() {
|
||||||
|
try {
|
||||||
|
// 注册中文语言包
|
||||||
|
countries.registerLocale(require('i18n-iso-countries/langs/zh.json'));
|
||||||
|
// 获取所有国家的中文名称
|
||||||
|
const countryNames = countries.getNames('zh', { select: 'official' });
|
||||||
|
// 格式化为 { code, name } 的数组
|
||||||
|
const countryList = Object.keys(countryNames).map(code => ({
|
||||||
|
code,
|
||||||
|
name: countryNames[code],
|
||||||
|
}));
|
||||||
|
return successResponse(countryList, '查询成功');
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ApiOperation({ summary: '创建区域' })
|
@ApiOperation({ summary: '创建区域' })
|
||||||
@ApiBody({ type: CreateAreaDTO })
|
@ApiBody({ type: CreateAreaDTO })
|
||||||
@ApiOkResponse({ type: Area, description: '成功创建的区域' })
|
@ApiOkResponse({ type: Area, description: '成功创建的区域' })
|
||||||
|
|
@ -39,7 +54,8 @@ export class AreaController {
|
||||||
const newArea = await this.areaService.createArea(area);
|
const newArea = await this.areaService.createArea(area);
|
||||||
return successResponse(newArea, '创建成功');
|
return successResponse(newArea, '创建成功');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error.message);
|
console.log(error);
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,7 +68,8 @@ export class AreaController {
|
||||||
const updatedArea = await this.areaService.updateArea(id, area);
|
const updatedArea = await this.areaService.updateArea(id, area);
|
||||||
return successResponse(updatedArea, '更新成功');
|
return successResponse(updatedArea, '更新成功');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error.message);
|
console.log(error);
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,7 +81,8 @@ export class AreaController {
|
||||||
await this.areaService.deleteArea(id);
|
await this.areaService.deleteArea(id);
|
||||||
return successResponse(null, '删除成功');
|
return successResponse(null, '删除成功');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error.message);
|
console.log(error);
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,19 +95,8 @@ export class AreaController {
|
||||||
const { list, total } = await this.areaService.getAreaList(query);
|
const { list, total } = await this.areaService.getAreaList(query);
|
||||||
return successResponse({ list, total }, '查询成功');
|
return successResponse({ list, total }, '查询成功');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error.message);
|
console.log(error);
|
||||||
}
|
return errorResponse(error?.message || error);
|
||||||
}
|
|
||||||
|
|
||||||
@ApiOperation({ summary: '获取所有区域' })
|
|
||||||
@ApiOkResponse({ type: [Area], description: '所有区域列表' })
|
|
||||||
@Get('/all')
|
|
||||||
async getAllAreas() {
|
|
||||||
try {
|
|
||||||
const areas = await this.areaService.getAllAreas();
|
|
||||||
return successResponse(areas, '查询成功');
|
|
||||||
} catch (error) {
|
|
||||||
return errorResponse(error.message);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,7 +111,8 @@ export class AreaController {
|
||||||
}
|
}
|
||||||
return successResponse(area, '查询成功');
|
return successResponse(area, '查询成功');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error.message);
|
console.log(error);
|
||||||
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -134,9 +134,9 @@ export class ProductController {
|
||||||
|
|
||||||
@ApiOkResponse({ type: ProductRes })
|
@ApiOkResponse({ type: ProductRes })
|
||||||
@Put('updateNameCn/:id/:nameCn')
|
@Put('updateNameCn/:id/:nameCn')
|
||||||
async updateProductNameCn(@Param('id') id: number, @Param('nameCn') nameCn: string) {
|
async updatenameCn(@Param('id') id: number, @Param('nameCn') nameCn: string) {
|
||||||
try {
|
try {
|
||||||
const data = this.productService.updateProductNameCn(id, nameCn);
|
const data = this.productService.updatenameCn(id, nameCn);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
|
|
|
||||||
|
|
@ -8,18 +8,21 @@ export default class AreaSeeder implements Seeder {
|
||||||
dataSource: DataSource,
|
dataSource: DataSource,
|
||||||
factoryManager: SeederFactoryManager
|
factoryManager: SeederFactoryManager
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
const repository = dataSource.getRepository(Area);
|
const areaRepository = dataSource.getRepository(Area);
|
||||||
|
|
||||||
const areas = [
|
const areas = [
|
||||||
{ name: '加拿大' },
|
{ name: 'Australia' },
|
||||||
{ name: '澳大利亚' },
|
{ name: 'Canada' },
|
||||||
{ name: '欧洲' },
|
{ name: 'United States' },
|
||||||
|
{ name: 'Germany' },
|
||||||
|
{ name: 'Poland' },
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const area of areas) {
|
for (const areaData of areas) {
|
||||||
const existing = await repository.findOne({ where: { name: area.name } });
|
const existingArea = await areaRepository.findOne({ where: { name: areaData.name } });
|
||||||
if (!existing) {
|
if (!existingArea) {
|
||||||
await repository.insert(area);
|
const newArea = areaRepository.create(areaData);
|
||||||
|
await areaRepository.save(newArea);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,9 @@ export default class DictSeeder implements Seeder {
|
||||||
* @returns 格式化后的名称
|
* @returns 格式化后的名称
|
||||||
*/
|
*/
|
||||||
private formatName(name: string): string {
|
private formatName(name: string): string {
|
||||||
// 将空格、下划线、点统一转换成中划线,并转为小写
|
// return String(name).replace(/[\_\s.]+/g, '-').toLowerCase();
|
||||||
return String(name).replace(/[_\s.]+/g, '-').toLowerCase();
|
// 只替换空格和下划线
|
||||||
|
return String(name).replace(/[\_\s]+/g, '-').toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async run(
|
public async run(
|
||||||
|
|
|
||||||
|
|
@ -2,47 +2,28 @@
|
||||||
import { ApiProperty } from '@midwayjs/swagger';
|
import { ApiProperty } from '@midwayjs/swagger';
|
||||||
import { Rule, RuleType } from '@midwayjs/validate';
|
import { Rule, RuleType } from '@midwayjs/validate';
|
||||||
|
|
||||||
// 创建区域的数据传输对象
|
|
||||||
export class CreateAreaDTO {
|
export class CreateAreaDTO {
|
||||||
@ApiProperty({ type: 'string', description: '区域名称', example: '欧洲' })
|
@ApiProperty({ description: '编码' })
|
||||||
@Rule(RuleType.string().required())
|
@Rule(RuleType.string().required())
|
||||||
name: string;
|
code: string;
|
||||||
|
|
||||||
@ApiProperty({ type: 'number', description: '纬度', example: 48.8566, required: false })
|
|
||||||
@Rule(RuleType.number().min(-90).max(90).allow(null))
|
|
||||||
latitude?: number;
|
|
||||||
|
|
||||||
@ApiProperty({ type: 'number', description: '经度', example: 2.3522, required: false })
|
|
||||||
@Rule(RuleType.number().min(-180).max(180).allow(null))
|
|
||||||
longitude?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新区域的数据传输对象
|
|
||||||
export class UpdateAreaDTO {
|
export class UpdateAreaDTO {
|
||||||
@ApiProperty({ type: 'string', description: '区域名称', example: '欧洲' })
|
@ApiProperty({ description: '编码', required: false })
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
name?: string;
|
code?: string;
|
||||||
|
|
||||||
@ApiProperty({ type: 'number', description: '纬度', example: 48.8566, required: false })
|
|
||||||
@Rule(RuleType.number().min(-90).max(90).allow(null))
|
|
||||||
latitude?: number;
|
|
||||||
|
|
||||||
@ApiProperty({ type: 'number', description: '经度', example: 2.3522, required: false })
|
|
||||||
@Rule(RuleType.number().min(-180).max(180).allow(null))
|
|
||||||
longitude?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询区域的数据传输对象
|
|
||||||
export class QueryAreaDTO {
|
export class QueryAreaDTO {
|
||||||
@ApiProperty({ type: 'number', description: '当前页码', example: 1 })
|
@ApiProperty({ description: '当前页', required: false, default: 1 })
|
||||||
@Rule(RuleType.number().min(1).default(1))
|
@Rule(RuleType.number().integer().min(1).default(1))
|
||||||
currentPage: number;
|
currentPage?: number;
|
||||||
|
|
||||||
@ApiProperty({ type: 'number', description: '每页数量', example: 10 })
|
@ApiProperty({ description: '每页数量', required: false, default: 10 })
|
||||||
@Rule(RuleType.number().min(1).max(100).default(10))
|
@Rule(RuleType.number().integer().min(1).default(10))
|
||||||
pageSize: number;
|
pageSize?: number;
|
||||||
|
|
||||||
@ApiProperty({ type: 'string', description: '区域名称', example: '欧洲' })
|
@ApiProperty({ description: '关键词(名称或编码)', required: false })
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
name?: string;
|
keyword?: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ export class QueryStockDTO {
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
productName: string;
|
name: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
|
|
@ -62,7 +62,7 @@ export class QueryStockRecordDTO {
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
productName: string;
|
name: string;
|
||||||
|
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
|
|
@ -96,7 +96,7 @@ export class QueryPurchaseOrderDTO {
|
||||||
export class StockDTO extends Stock {
|
export class StockDTO extends Stock {
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
@Rule(RuleType.string())
|
@Rule(RuleType.string())
|
||||||
productName: string;
|
name: string;
|
||||||
|
|
||||||
@ApiProperty({
|
@ApiProperty({
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,17 @@
|
||||||
|
|
||||||
import { ApiProperty } from '@midwayjs/swagger';
|
import { ApiProperty } from '@midwayjs/swagger';
|
||||||
import {
|
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||||
Column,
|
|
||||||
CreateDateColumn,
|
|
||||||
Entity,
|
|
||||||
PrimaryGeneratedColumn,
|
|
||||||
UpdateDateColumn,
|
|
||||||
} from 'typeorm';
|
|
||||||
|
|
||||||
@Entity('area')
|
@Entity('area')
|
||||||
export class Area {
|
export class Area {
|
||||||
@ApiProperty({ type: 'number' })
|
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
@ApiProperty({ type: 'string', description: '区域名称' })
|
@ApiProperty({ description: '名称' })
|
||||||
@Column({ unique: true })
|
@Column()
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@ApiProperty({ type: 'number', description: '纬度', required: false })
|
@ApiProperty({ description: '编码' })
|
||||||
@Column({ type: 'decimal', precision: 10, scale: 6, nullable: true })
|
@Column({ unique: true })
|
||||||
latitude?: number;
|
code: string;
|
||||||
|
|
||||||
@ApiProperty({ type: 'number', description: '经度', required: false })
|
|
||||||
@Column({ type: 'decimal', precision: 10, scale: 6, nullable: true })
|
|
||||||
longitude?: number;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
example: '2022-12-12 11:11:11',
|
|
||||||
description: '创建时间',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@CreateDateColumn()
|
|
||||||
createdAt: Date;
|
|
||||||
|
|
||||||
@ApiProperty({
|
|
||||||
example: '2022-12-12 11:11:11',
|
|
||||||
description: '更新时间',
|
|
||||||
required: true,
|
|
||||||
})
|
|
||||||
@UpdateDateColumn()
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ export class PurchaseOrderItem {
|
||||||
|
|
||||||
@ApiProperty({ type: String })
|
@ApiProperty({ type: String })
|
||||||
@Column()
|
@Column()
|
||||||
productName: string;
|
name: string;
|
||||||
|
|
||||||
@ApiProperty({ type: Number })
|
@ApiProperty({ type: Number })
|
||||||
@Column()
|
@Column()
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ export class TransferItem {
|
||||||
|
|
||||||
@ApiProperty({ type: String })
|
@ApiProperty({ type: String })
|
||||||
@Column()
|
@Column()
|
||||||
productName: string;
|
name: string;
|
||||||
|
|
||||||
@ApiProperty({ type: Number })
|
@ApiProperty({ type: Number })
|
||||||
@Column()
|
@Column()
|
||||||
|
|
|
||||||
|
|
@ -1,102 +1,80 @@
|
||||||
|
|
||||||
import { Provide } from '@midwayjs/core';
|
import { Provide } from '@midwayjs/core';
|
||||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Like, Repository } from 'typeorm';
|
||||||
import { Area } from '../entity/area.entity';
|
import { Area } from '../entity/area.entity';
|
||||||
import { CreateAreaDTO, QueryAreaDTO, UpdateAreaDTO } from '../dto/area.dto';
|
import { CreateAreaDTO, QueryAreaDTO, UpdateAreaDTO } from '../dto/area.dto';
|
||||||
|
import * as countries from 'i18n-iso-countries';
|
||||||
|
|
||||||
@Provide()
|
@Provide()
|
||||||
export class AreaService {
|
export class AreaService {
|
||||||
@InjectEntityModel(Area)
|
@InjectEntityModel(Area)
|
||||||
areaModel: Repository<Area>;
|
areaRepository: Repository<Area>;
|
||||||
|
|
||||||
/**
|
constructor() {
|
||||||
* 创建区域
|
// 在服务初始化时注册中文语言包
|
||||||
* @param params 创建参数
|
countries.registerLocale(require('i18n-iso-countries/langs/zh.json'));
|
||||||
*/
|
|
||||||
async createArea(params: CreateAreaDTO) {
|
|
||||||
// 检查区域名称是否已存在
|
|
||||||
const existing = await this.areaModel.findOne({ where: { name: params.name } });
|
|
||||||
if (existing) {
|
|
||||||
throw new Error('区域名称已存在');
|
|
||||||
}
|
|
||||||
const area = new Area();
|
|
||||||
area.name = params.name;
|
|
||||||
if (params.latitude !== undefined) {
|
|
||||||
area.latitude = params.latitude;
|
|
||||||
}
|
|
||||||
if (params.longitude !== undefined) {
|
|
||||||
area.longitude = params.longitude;
|
|
||||||
}
|
|
||||||
return await this.areaModel.save(area);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新区域
|
|
||||||
* @param id 区域ID
|
|
||||||
* @param params 更新参数
|
|
||||||
*/
|
|
||||||
async updateArea(id: number, params: UpdateAreaDTO) {
|
|
||||||
const area = await this.areaModel.findOneBy({ id });
|
|
||||||
if (!area) {
|
|
||||||
throw new Error('区域不存在');
|
|
||||||
}
|
|
||||||
if (params.name) {
|
|
||||||
// 检查新的区域名称是否已存在
|
|
||||||
const existing = await this.areaModel.findOne({ where: { name: params.name } });
|
|
||||||
if (existing && existing.id !== id) {
|
|
||||||
throw new Error('区域名称已存在');
|
|
||||||
}
|
|
||||||
area.name = params.name;
|
|
||||||
}
|
|
||||||
if (params.latitude !== undefined) {
|
|
||||||
area.latitude = params.latitude;
|
|
||||||
}
|
|
||||||
if (params.longitude !== undefined) {
|
|
||||||
area.longitude = params.longitude;
|
|
||||||
}
|
|
||||||
return await this.areaModel.save(area);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除区域
|
|
||||||
* @param id 区域ID
|
|
||||||
*/
|
|
||||||
async deleteArea(id: number) {
|
|
||||||
const result = await this.areaModel.delete(id);
|
|
||||||
return result.affected > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取区域列表(分页)
|
|
||||||
* @param query 查询参数
|
|
||||||
*/
|
|
||||||
async getAreaList(query: QueryAreaDTO) {
|
async getAreaList(query: QueryAreaDTO) {
|
||||||
const { currentPage, pageSize, name } = query;
|
const { currentPage = 1, pageSize = 10, keyword = '' } = query;
|
||||||
const [list, total] = await this.areaModel.findAndCount({
|
const [list, total] = await this.areaRepository.findAndCount({
|
||||||
where: name ? { name } : {},
|
where: [{ name: Like(`%${keyword}%`) }, { code: Like(`%${keyword}%`) }],
|
||||||
skip: (currentPage - 1) * pageSize,
|
skip: (currentPage - 1) * pageSize,
|
||||||
take: pageSize,
|
take: pageSize,
|
||||||
order: {
|
|
||||||
id: 'DESC',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return { list, total };
|
return { list, total };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async getAreaById(id: number) {
|
||||||
* 获取所有区域
|
return this.areaRepository.findOne({ where: { id } });
|
||||||
*/
|
|
||||||
async getAllAreas() {
|
|
||||||
return await this.areaModel.find();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async createArea(createAreaDTO: CreateAreaDTO) {
|
||||||
* 根据ID获取区域详情
|
// 根据 code 获取国家中文名称
|
||||||
* @param id 区域ID
|
const name = countries.getName(createAreaDTO.code, 'zh', {
|
||||||
*/
|
select: 'official',
|
||||||
async getAreaById(id: number) {
|
});
|
||||||
return await this.areaModel.findOneBy({ id });
|
|
||||||
|
// 如果找不到对应的国家,则抛出错误
|
||||||
|
if (!name) {
|
||||||
|
throw new Error(`无效的国家代码: ${createAreaDTO.code}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const area = new Area();
|
||||||
|
area.name = name;
|
||||||
|
area.code = createAreaDTO.code;
|
||||||
|
return this.areaRepository.save(area);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateArea(id: number, updateAreaDTO: UpdateAreaDTO) {
|
||||||
|
const area = await this.getAreaById(id);
|
||||||
|
if (!area) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果 code 发生变化,则更新 name
|
||||||
|
if (updateAreaDTO.code && updateAreaDTO.code !== area.code) {
|
||||||
|
const name = countries.getName(updateAreaDTO.code, 'zh', {
|
||||||
|
select: 'official',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
throw new Error(`无效的国家代码: ${updateAreaDTO.code}`);
|
||||||
|
}
|
||||||
|
area.name = name;
|
||||||
|
area.code = updateAreaDTO.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.areaRepository.save(area);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteArea(id: number) {
|
||||||
|
const area = await this.getAreaById(id);
|
||||||
|
if (!area) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
await this.areaRepository.remove(area);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ export class DictService {
|
||||||
|
|
||||||
// 格式化名称为 kebab-case
|
// 格式化名称为 kebab-case
|
||||||
private formatName(name: string): string {
|
private formatName(name: string): string {
|
||||||
return String(name).replace(/[_\s.]+/g, '-').toLowerCase();
|
// 只替换空格和下划线
|
||||||
|
return String(name).replace(/[_\s]+/g, '-').toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成并返回字典的XLSX模板
|
// 生成并返回字典的XLSX模板
|
||||||
|
|
|
||||||
|
|
@ -509,7 +509,7 @@ export class ProductService {
|
||||||
|
|
||||||
// 重复定义的 getProductList 已合并到前面的实现(中文注释:移除重复)
|
// 重复定义的 getProductList 已合并到前面的实现(中文注释:移除重复)
|
||||||
|
|
||||||
async updateProductNameCn(id: number, nameCn: string): Promise<Product> {
|
async updatenameCn(id: number, nameCn: string): Promise<Product> {
|
||||||
// 确认产品是否存在
|
// 确认产品是否存在
|
||||||
const product = await this.productModel.findOneBy({ id });
|
const product = await this.productModel.findOneBy({ id });
|
||||||
if (!product) {
|
if (!product) {
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,7 @@ export class StockService {
|
||||||
qb
|
qb
|
||||||
.select([
|
.select([
|
||||||
'poi.purchaseOrderId AS purchaseOrderId',
|
'poi.purchaseOrderId AS purchaseOrderId',
|
||||||
"JSON_ARRAYAGG(JSON_OBJECT('id', poi.id, 'productName', poi.productName,'sku', poi.sku, 'quantity', poi.quantity, 'price', poi.price)) AS items",
|
"JSON_ARRAYAGG(JSON_OBJECT('id', poi.id, 'name', poi.name,'sku', poi.sku, 'quantity', poi.quantity, 'price', poi.price)) AS items",
|
||||||
])
|
])
|
||||||
.from(PurchaseOrderItem, 'poi')
|
.from(PurchaseOrderItem, 'poi')
|
||||||
.groupBy('poi.purchaseOrderId'),
|
.groupBy('poi.purchaseOrderId'),
|
||||||
|
|
@ -240,9 +240,9 @@ export class StockService {
|
||||||
|
|
||||||
// 获取库存列表
|
// 获取库存列表
|
||||||
async getStocks(query: QueryStockDTO) {
|
async getStocks(query: QueryStockDTO) {
|
||||||
const { current = 1, pageSize = 10, productName, sku } = query;
|
const { current = 1, pageSize = 10, name, sku } = query;
|
||||||
const nameKeywords = productName
|
const nameKeywords = name
|
||||||
? productName.split(' ').filter(Boolean)
|
? name.split(' ').filter(Boolean)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
let queryBuilder = this.stockModel
|
let queryBuilder = this.stockModel
|
||||||
|
|
@ -250,8 +250,8 @@ export class StockService {
|
||||||
.select([
|
.select([
|
||||||
// 'stock.id as id',
|
// 'stock.id as id',
|
||||||
'stock.sku as sku',
|
'stock.sku as sku',
|
||||||
'product.name as productName',
|
'product.name as name',
|
||||||
'product.nameCn as productNameCn',
|
'product.nameCn as nameCn',
|
||||||
'JSON_ARRAYAGG(JSON_OBJECT("id", stock.stockPointId, "quantity", stock.quantity)) as stockPoint',
|
'JSON_ARRAYAGG(JSON_OBJECT("id", stock.stockPointId, "quantity", stock.quantity)) as stockPoint',
|
||||||
'MIN(stock.updatedAt) as updatedAt',
|
'MIN(stock.updatedAt) as updatedAt',
|
||||||
'MAX(stock.createdAt) as createdAt',
|
'MAX(stock.createdAt) as createdAt',
|
||||||
|
|
@ -264,33 +264,29 @@ export class StockService {
|
||||||
.createQueryBuilder('stock')
|
.createQueryBuilder('stock')
|
||||||
.select('COUNT(DISTINCT stock.sku)', 'count')
|
.select('COUNT(DISTINCT stock.sku)', 'count')
|
||||||
.leftJoin(Product, 'product', 'product.sku = stock.sku');
|
.leftJoin(Product, 'product', 'product.sku = stock.sku');
|
||||||
if (sku) {
|
if (sku || nameKeywords.length) {
|
||||||
queryBuilder.andWhere('stock.sku = :sku', { sku });
|
const conditions = [];
|
||||||
totalQueryBuilder.andWhere('stock.sku = :sku', { sku });
|
if (sku) {
|
||||||
}
|
conditions.push(`stock.sku LIKE :sku`);
|
||||||
if (nameKeywords.length) {
|
}
|
||||||
nameKeywords.forEach((name, index) => {
|
if (nameKeywords.length) {
|
||||||
queryBuilder.andWhere(
|
nameKeywords.forEach((name, index) => {
|
||||||
`EXISTS (
|
conditions.push(`product.name LIKE :name${index}`);
|
||||||
SELECT 1 FROM product p
|
});
|
||||||
WHERE p.sku = stock.sku
|
}
|
||||||
AND p.name LIKE :name${index}
|
const whereClause = conditions.join(' OR ');
|
||||||
)`,
|
queryBuilder.andWhere(`(${whereClause})`, {
|
||||||
{ [`name${index}`]: `%${name}%` }
|
sku: `%${sku}%`,
|
||||||
);
|
...nameKeywords.reduce((acc, name, index) => ({ ...acc, [`name${index}`]: `%${name}%` }), {}),
|
||||||
totalQueryBuilder.andWhere(
|
});
|
||||||
`EXISTS (
|
totalQueryBuilder.andWhere(`(${whereClause})`, {
|
||||||
SELECT 1 FROM product p
|
sku: `%${sku}%`,
|
||||||
WHERE p.sku = stock.sku
|
...nameKeywords.reduce((acc, name, index) => ({ ...acc, [`name${index}`]: `%${name}%` }), {}),
|
||||||
AND p.name LIKE :name${index}
|
|
||||||
)`,
|
|
||||||
{ [`name${index}`]: `%${name}%` }
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (query.order) {
|
if (query.order) {
|
||||||
const sortFieldMap: Record<string, string> = {
|
const sortFieldMap: Record<string, string> = {
|
||||||
productName: 'product.name',
|
name: 'product.name',
|
||||||
sku: 'stock.sku',
|
sku: 'stock.sku',
|
||||||
updatedAt: 'updatedAt',
|
updatedAt: 'updatedAt',
|
||||||
createdAt: 'createdAt',
|
createdAt: 'createdAt',
|
||||||
|
|
@ -422,7 +418,7 @@ export class StockService {
|
||||||
pageSize = 10,
|
pageSize = 10,
|
||||||
stockPointId,
|
stockPointId,
|
||||||
sku,
|
sku,
|
||||||
productName,
|
name,
|
||||||
operationType,
|
operationType,
|
||||||
startDate,
|
startDate,
|
||||||
endDate,
|
endDate,
|
||||||
|
|
@ -447,9 +443,9 @@ export class StockService {
|
||||||
'sp.name as stockPointName',
|
'sp.name as stockPointName',
|
||||||
])
|
])
|
||||||
.where(where);
|
.where(where);
|
||||||
if (productName)
|
if (name)
|
||||||
queryBuilder.andWhere('product.name LIKE :name', {
|
queryBuilder.andWhere('product.name LIKE :name', {
|
||||||
name: `%${productName}%`,
|
name: `%${name}%`,
|
||||||
});
|
});
|
||||||
const items = await queryBuilder
|
const items = await queryBuilder
|
||||||
.orderBy('stock_record.createdAt', 'DESC')
|
.orderBy('stock_record.createdAt', 'DESC')
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue