Compare commits

..

No commits in common. "b44a108bc08cbbc272938b04cb515053fdf790de" and "2df310b1c0b5985c34a852435a45b8b9a2eb796d" have entirely different histories.

73 changed files with 7224 additions and 10717 deletions

View File

@ -28,18 +28,16 @@ RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
# see https://pnpm.io/docker # see https://pnpm.io/docker
FROM base AS prod-deps FROM base AS prod-deps
RUN pnpm config set registry https://registry.npmmirror.com
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
FROM base AS build FROM base AS build
RUN pnpm config set registry https://registry.npmmirror.com
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
RUN pnpm run build RUN pnpm run build
# mirror acceleration # mirror acceleration
# RUN npm config set registry https://registry.npmmirror.com # RUN npm config set registry https://registry.npmmirror.com
# RUN pnpm config set registry https://registry.npmmirror.com
# RUN npm config rm proxy && npm config rm https-proxy # RUN npm config rm proxy && npm config rm https-proxy
FROM base FROM base

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
"name": "huaxin-admin", "name": "huaxin-admin",
"version": "2.0.0", "version": "2.0.0",
"private": true, "private": true,
"packageManager": "pnpm@9.1.4", "packageManager": "pnpm@8.10.2",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=18", "node": ">=18",

File diff suppressed because it is too large Load Diff

View File

@ -1,38 +1,35 @@
import {ClassSerializerInterceptor, Module} from '@nestjs/common'; import { ClassSerializerInterceptor, Module } from '@nestjs/common';
import {ConfigModule} from '@nestjs/config'; import { ConfigModule } from '@nestjs/config';
import {APP_FILTER, APP_GUARD, APP_INTERCEPTOR} from '@nestjs/core'; import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
import config from '~/config'; import config from '~/config';
import {SharedModule} from '~/shared/shared.module'; import { SharedModule } from '~/shared/shared.module';
import {AllExceptionsFilter} from './common/filters/any-exception.filter'; import { AllExceptionsFilter } from './common/filters/any-exception.filter';
import {IdempotenceInterceptor} from './common/interceptors/idempotence.interceptor'; import { IdempotenceInterceptor } from './common/interceptors/idempotence.interceptor';
import {TimeoutInterceptor} from './common/interceptors/timeout.interceptor'; import { TimeoutInterceptor } from './common/interceptors/timeout.interceptor';
import {TransformInterceptor} from './common/interceptors/transform.interceptor'; import { TransformInterceptor } from './common/interceptors/transform.interceptor';
import {AuthModule} from './modules/auth/auth.module'; import { AuthModule } from './modules/auth/auth.module';
import {JwtAuthGuard} from './modules/auth/guards/jwt-auth.guard'; import { JwtAuthGuard } from './modules/auth/guards/jwt-auth.guard';
import {RbacGuard} from './modules/auth/guards/rbac.guard'; import { RbacGuard } from './modules/auth/guards/rbac.guard';
import {HealthModule} from './modules/health/health.module'; import { HealthModule } from './modules/health/health.module';
import {NetdiskModule} from './modules/netdisk/netdisk.module'; import { NetdiskModule } from './modules/netdisk/netdisk.module';
import {SseModule} from './modules/sse/sse.module'; import { SseModule } from './modules/sse/sse.module';
import {SystemModule} from './modules/system/system.module'; import { SystemModule } from './modules/system/system.module';
import {TasksModule} from './modules/tasks/tasks.module'; import { TasksModule } from './modules/tasks/tasks.module';
import {TodoModule} from './modules/todo/todo.module'; import { TodoModule } from './modules/todo/todo.module';
import {ToolsModule} from './modules/tools/tools.module'; import { ToolsModule } from './modules/tools/tools.module';
import {DatabaseModule} from './shared/database/database.module'; import { DatabaseModule } from './shared/database/database.module';
import {SocketModule} from './socket/socket.module'; import { SocketModule } from './socket/socket.module';
import {ContractModule} from './modules/contract/contract.module'; import { ContractModule } from './modules/contract/contract.module';
import {MaterialsInventoryModule} from './modules/materials_inventory/materials_inventory.module'; import { MaterialsInventoryModule } from './modules/materials_inventory/materials_inventory.module';
import {CompanyModule} from './modules/company/company.module'; import { CompanyModule } from './modules/company/company.module';
import {ProductModule} from './modules/product/product.module'; import { ProductModule } from './modules/product/product.module';
import {ProjectModule} from './modules/project/project.module'; import { ProjectModule } from './modules/project/project.module';
import {VehicleUsageModule} from './modules/vehicle_usage/vehicle_usage.module'; import { VehicleUsageModule } from './modules/vehicle_usage/vehicle_usage.module';
import {SaleQuotationModule} from './modules/sale_quotation/sale_quotation.module';
import {DomainModule} from './modules/domian/domain.module';
import {BabyRecordModule} from "~/modules/baby_record/baby_record.module";
@Module({ @Module({
imports: [ imports: [
@ -76,27 +73,19 @@ import {BabyRecordModule} from "~/modules/baby_record/baby_record.module";
ProjectModule, ProjectModule,
// 车辆管理 // 车辆管理
VehicleUsageModule, VehicleUsageModule
//报价管理
SaleQuotationModule,
//域
DomainModule,
// 宝宝管理
BabyRecordModule
], ],
providers: [ providers: [
{provide: APP_FILTER, useClass: AllExceptionsFilter}, { provide: APP_FILTER, useClass: AllExceptionsFilter },
{provide: APP_INTERCEPTOR, useClass: ClassSerializerInterceptor}, { provide: APP_INTERCEPTOR, useClass: ClassSerializerInterceptor },
{provide: APP_INTERCEPTOR, useClass: TransformInterceptor}, { provide: APP_INTERCEPTOR, useClass: TransformInterceptor },
{provide: APP_INTERCEPTOR, useFactory: () => new TimeoutInterceptor(15 * 1000)}, { provide: APP_INTERCEPTOR, useFactory: () => new TimeoutInterceptor(15 * 1000) },
{provide: APP_INTERCEPTOR, useClass: IdempotenceInterceptor}, { provide: APP_INTERCEPTOR, useClass: IdempotenceInterceptor },
{provide: APP_GUARD, useClass: JwtAuthGuard}, { provide: APP_GUARD, useClass: JwtAuthGuard },
{provide: APP_GUARD, useClass: RbacGuard} { provide: APP_GUARD, useClass: RbacGuard }
], ],
controllers: [] controllers: []
}) })
export class AppModule { export class AppModule {}
}

View File

@ -13,7 +13,7 @@ app.register(FastifyMultipart, {
attachFieldsToBody:true, attachFieldsToBody:true,
limits: { limits: {
fields: 10, // Max number of non-file fields fields: 10, // Max number of non-file fields
fileSize: 1024 * 1024 * 50, // limit size 50M fileSize: 1024 * 1024 * 30, // limit size 6M
files: 5 // Max number of file fields files: 5 // Max number of file fields
} }
}); });

View File

@ -1,18 +0,0 @@
import { ExecutionContext, createParamDecorator } from '@nestjs/common';
import { ApiProperty } from '@nestjs/swagger';
import { IsOptional } from 'class-validator';
import type { FastifyRequest } from 'fastify';
/**
*
*/
export const Domain = createParamDecorator((_, context: ExecutionContext) => {
const request = context.switchToHttp().getRequest<FastifyRequest>();
return request.headers['sk-domain'] ?? 1;
});
export type SkDomain = number;
export class DomainType {
@ApiProperty({ description: '所属域' })
@IsOptional()
domain: SkDomain;
}

View File

@ -5,7 +5,6 @@ import type { FastifyRequest } from 'fastify';
import { getIp, getIsMobile } from '~/utils/ip.util'; import { getIp, getIsMobile } from '~/utils/ip.util';
/** /**
* IP * IP
*/ */

View File

@ -40,14 +40,3 @@ export enum HasInventoryStatusEnum {
Yes = 1, // 有库存 Yes = 1, // 有库存
No = 2 // 无库存 No = 2 // 无库存
} }
// 权限资源设备类型
export enum ResourceDeviceEnum {
APP = 0,
PC = 1
}
// 宝宝动作
export enum BabyActionEnum {
Pee, Shit, Breast, Bottle
}

View File

@ -61,12 +61,5 @@ export enum ErrorEnum {
INVENTORY_INSUFFICIENT = '1408:库存数量不足。请检查库存或重新操作', INVENTORY_INSUFFICIENT = '1408:库存数量不足。请检查库存或重新操作',
MATERIALS_IN_OUT_NOT_FOUND = '1409:出入库信息不存在', MATERIALS_IN_OUT_NOT_FOUND = '1409:出入库信息不存在',
MATERIALS_IN_OUT_UNIT_PRICE_CANNOT_BE_MODIFIED = '1410:该价格的产品已经出库,单价不允许修改。若有疑问,请联系管理员', MATERIALS_IN_OUT_UNIT_PRICE_CANNOT_BE_MODIFIED = '1410:该价格的产品已经出库,单价不允许修改。若有疑问,请联系管理员',
MATERIALS_IN_OUT_UNIT_PRICE_MUST_ZERO_WHEN_MODIFIED = '1411:只能修改初始单价为0的入库记录。 若有疑问,请联系管理员', MATERIALS_IN_OUT_UNIT_PRICE_MUST_ZERO_WHEN_MODIFIED = '1411:只能修改初始单价为0的入库记录。 若有疑问,请联系管理员'
// SaleQuotation
SALE_QUOTATION_COMPONENT_DUPLICATED = '1412:存在名称,价格,规格都相同的配件,请检查是否重复录入',
SALE_QUOTATION_TEMPLATE_NAME_DUPLICATE = '1413:模板名已存在',
//domain
DOMAIN_TITLE_DUPLICATE = '1414:域标题已存在'
} }

View File

@ -14,7 +14,6 @@ import { LoginToken } from './models/auth.model';
import { CaptchaService } from './services/captcha.service'; import { CaptchaService } from './services/captcha.service';
import { AuthUser } from './decorators/auth-user.decorator'; import { AuthUser } from './decorators/auth-user.decorator';
import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator'; import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator';
import { Domain, SkDomain } from '~/common/decorators/domain.decorator';
@ApiTags('Auth - 认证模块') @ApiTags('Auth - 认证模块')
@UseGuards(LocalGuard) @UseGuards(LocalGuard)
@ -36,7 +35,7 @@ export class AuthController {
@IsMobile() isMobile: boolean, @IsMobile() isMobile: boolean,
@Headers('user-agent') ua: string @Headers('user-agent') ua: string
): Promise<LoginToken> { ): Promise<LoginToken> {
if (!isMobile) { if(!isMobile){
await this.captchaService.checkImgCaptcha(dto.captchaId, dto.verifyCode); await this.captchaService.checkImgCaptcha(dto.captchaId, dto.verifyCode);
} }
const token = await this.authService.login(dto.username, dto.password, ip, ua); const token = await this.authService.login(dto.username, dto.password, ip, ua);
@ -54,7 +53,7 @@ export class AuthController {
@Post('register') @Post('register')
@ApiOperation({ summary: '注册' }) @ApiOperation({ summary: '注册' })
async register(@Domain() domain: SkDomain, @Body() dto: RegisterDto): Promise<void> { async register(@Body() dto: RegisterDto): Promise<void> {
await this.userService.register(dto, domain); await this.userService.register(dto);
} }
} }

View File

@ -132,8 +132,8 @@ export class AuthService {
/** /**
* *
*/ */
async getMenus(uid: number, isApp: number): Promise<string[]> { async getMenus(uid: number): Promise<string[]> {
return this.menuService.getMenus(uid,isApp); return this.menuService.getMenus(uid);
} }
/** /**

View File

@ -14,9 +14,6 @@ import { UserService } from '../../user/user.service';
import { AuthService } from '../auth.service'; import { AuthService } from '../auth.service';
import { AccountMenus, AccountUpdateDto } from '../dto/account.dto'; import { AccountMenus, AccountUpdateDto } from '../dto/account.dto';
import { JwtAuthGuard } from '../guards/jwt-auth.guard'; import { JwtAuthGuard } from '../guards/jwt-auth.guard';
import { IsMobile } from '~/common/decorators/http.decorator';
import { ResourceDeviceEnum } from '~/constants/enum';
import { Domain } from '~/common/decorators/domain.decorator';
@ApiTags('Account - 账户模块') @ApiTags('Account - 账户模块')
@ApiSecurityAuth() @ApiSecurityAuth()
@ -48,11 +45,8 @@ export class AccountController {
@ApiOperation({ summary: '获取菜单列表' }) @ApiOperation({ summary: '获取菜单列表' })
@ApiResult({ type: [AccountMenus] }) @ApiResult({ type: [AccountMenus] })
@AllowAnon() @AllowAnon()
async menu(@AuthUser() user: IAuthUser, @IsMobile() isApp: boolean): Promise<string[]> { async menu(@AuthUser() user: IAuthUser): Promise<string[]> {
return this.authService.getMenus( return this.authService.getMenus(user.uid);
user.uid,
isApp ? ResourceDeviceEnum.APP : ResourceDeviceEnum.PC
);
} }
@Get('permissions') @Get('permissions')

View File

@ -1,22 +0,0 @@
import {Module} from '@nestjs/common';
import {TypeOrmModule} from '@nestjs/typeorm';
import {StorageModule} from '../tools/storage/storage.module';
import {ParamConfigModule} from '../system/param-config/param-config.module';
import {ProjectModule} from '../project/project.module';
import {ProjectEntity} from '../project/project.entity';
import {BabyRecordEntity} from "~/modules/baby_record/baby_record.entity";
import {BabyRecordService} from "~/modules/baby_record/baby_record.service";
import {BabyRecordController} from "~/modules/baby_record/baby_record.controller";
@Module({
imports: [
TypeOrmModule.forFeature([BabyRecordEntity,]),
ParamConfigModule,
StorageModule,
ProjectModule
],
controllers: [BabyRecordController],
providers: [BabyRecordService]
})
export class BabyRecordModule {
}

View File

@ -16,7 +16,6 @@ import { ApiResult } from '~/common/decorators/api-result.decorator';
import { CompanyEntity } from './company.entity'; import { CompanyEntity } from './company.entity';
import { CompanyDto, CompanyQueryDto, CompanyUpdateDto } from './company.dto'; import { CompanyDto, CompanyQueryDto, CompanyUpdateDto } from './company.dto';
import { IdParam } from '~/common/decorators/id-param.decorator'; import { IdParam } from '~/common/decorators/id-param.decorator';
import { Domain, SkDomain } from '~/common/decorators/domain.decorator';
export const permissions = definePermission('app:company', { export const permissions = definePermission('app:company', {
LIST: 'list', LIST: 'list',
CREATE: 'create', CREATE: 'create',
@ -35,8 +34,8 @@ export class CompanyController {
@ApiOperation({ summary: '获取公司列表' }) @ApiOperation({ summary: '获取公司列表' })
@ApiResult({ type: [CompanyEntity], isPage: true }) @ApiResult({ type: [CompanyEntity], isPage: true })
@Perm(permissions.LIST) @Perm(permissions.LIST)
async list(@Domain() domain: SkDomain, @Query() dto: CompanyQueryDto) { async list(@Query() dto: CompanyQueryDto) {
return this.companyService.findAll({ ...dto, domain }); return this.companyService.findAll(dto);
} }
@Get(':id') @Get(':id')
@ -50,8 +49,8 @@ export class CompanyController {
@Post() @Post()
@ApiOperation({ summary: '新增公司' }) @ApiOperation({ summary: '新增公司' })
@Perm(permissions.CREATE) @Perm(permissions.CREATE)
async create(@Domain() domain: SkDomain, @Body() dto: CompanyDto): Promise<void> { async create(@Body() dto: CompanyDto): Promise<void> {
await this.companyService.create({ ...dto, domain }); await this.companyService.create(dto);
} }
@Put(':id') @Put(':id')

View File

@ -15,9 +15,8 @@ import { PagerDto } from '~/common/dto/pager.dto';
import { Storage } from '../tools/storage/storage.entity'; import { Storage } from '../tools/storage/storage.entity';
import { IsUnique } from '~/shared/database/constraints/unique.constraint'; import { IsUnique } from '~/shared/database/constraints/unique.constraint';
import { CompanyEntity } from './company.entity'; import { CompanyEntity } from './company.entity';
import { DomainType, SkDomain } from '~/common/decorators/domain.decorator';
export class CompanyDto extends DomainType { export class CompanyDto {
@ApiProperty({ description: '公司名称' }) @ApiProperty({ description: '公司名称' })
@IsUnique(CompanyEntity, { message: '已存在同名公司' }) @IsUnique(CompanyEntity, { message: '已存在同名公司' })
@IsString() @IsString()
@ -43,8 +42,7 @@ export class ComapnyCreateDto extends PartialType(CompanyDto) {
export class CompanyQueryDto extends IntersectionType( export class CompanyQueryDto extends IntersectionType(
PagerDto<CompanyDto>, PagerDto<CompanyDto>,
PartialType(CompanyDto), PartialType(CompanyDto)
DomainType
) { ) {
@ApiProperty({ description: '公司名称' }) @ApiProperty({ description: '公司名称' })
@IsOptional() @IsOptional()

View File

@ -3,7 +3,6 @@ import { Column, Entity, JoinTable, ManyToMany, OneToMany, Relation } from 'type
import { CommonEntity } from '~/common/entity/common.entity'; import { CommonEntity } from '~/common/entity/common.entity';
import { Storage } from '../tools/storage/storage.entity'; import { Storage } from '../tools/storage/storage.entity';
import { ProductEntity } from '../product/product.entity'; import { ProductEntity } from '../product/product.entity';
import { SkDomain } from '~/common/decorators/domain.decorator';
@Entity({ name: 'company' }) @Entity({ name: 'company' })
export class CompanyEntity extends CommonEntity { export class CompanyEntity extends CommonEntity {
@ -21,10 +20,6 @@ export class CompanyEntity extends CommonEntity {
@ApiProperty({ description: '删除状态0未删除1已删除' }) @ApiProperty({ description: '删除状态0未删除1已删除' })
isDelete: number; isDelete: number;
@Column({ type: 'int', default: 1, comment: '所属域' })
@ApiProperty({ description: '所属域' })
domain: SkDomain;
@ApiHideProperty() @ApiHideProperty()
@OneToMany(() => ProductEntity, product => product.company) @OneToMany(() => ProductEntity, product => product.company)
products: Relation<ProductEntity[]>; products: Relation<ProductEntity[]>;

View File

@ -9,7 +9,6 @@ import { Storage } from '../tools/storage/storage.entity';
import { BusinessException } from '~/common/exceptions/biz.exception'; import { BusinessException } from '~/common/exceptions/biz.exception';
import { ErrorEnum } from '~/constants/error-code.constant'; import { ErrorEnum } from '~/constants/error-code.constant';
import { fieldSearch } from '~/shared/database/field-search'; import { fieldSearch } from '~/shared/database/field-search';
import { SkDomain } from '~/common/decorators/domain.decorator';
@Injectable() @Injectable()
export class CompanyService { export class CompanyService {
@ -71,7 +70,11 @@ export class CompanyService {
throw new BusinessException(ErrorEnum.STORAGE_NOT_FOUND); throw new BusinessException(ErrorEnum.STORAGE_NOT_FOUND);
} }
// 附件要批量插入 // 附件要批量插入
await manager.createQueryBuilder().relation(CompanyEntity, 'files').of(id).add(fileIds); await manager
.createQueryBuilder()
.relation(CompanyEntity, 'files')
.of(id)
.add(fileIds);
} }
}); });
} }

View File

@ -16,7 +16,6 @@ import { ApiResult } from '~/common/decorators/api-result.decorator';
import { ContractEntity } from './contract.entity'; import { ContractEntity } from './contract.entity';
import { ContractDto, ContractQueryDto, ContractUpdateDto } from './contract.dto'; import { ContractDto, ContractQueryDto, ContractUpdateDto } from './contract.dto';
import { IdParam } from '~/common/decorators/id-param.decorator'; import { IdParam } from '~/common/decorators/id-param.decorator';
import { Domain, SkDomain } from '~/common/decorators/domain.decorator';
export const permissions = definePermission('app:contract', { export const permissions = definePermission('app:contract', {
LIST: 'list', LIST: 'list',
CREATE: 'create', CREATE: 'create',
@ -35,8 +34,8 @@ export class ContractController {
@ApiOperation({ summary: '获取合同列表' }) @ApiOperation({ summary: '获取合同列表' })
@ApiResult({ type: [ContractEntity], isPage: true }) @ApiResult({ type: [ContractEntity], isPage: true })
@Perm(permissions.LIST) @Perm(permissions.LIST)
async list(@Domain() domain: SkDomain, @Query() dto: ContractQueryDto) { async list(@Query() dto: ContractQueryDto) {
return this.contractService.findAll({ ...dto, domain }); return this.contractService.findAll(dto);
} }
@Get(':id') @Get(':id')
@ -50,8 +49,8 @@ export class ContractController {
@Post() @Post()
@ApiOperation({ summary: '新增合同' }) @ApiOperation({ summary: '新增合同' })
@Perm(permissions.CREATE) @Perm(permissions.CREATE)
async create(@Domain() domain: SkDomain, @Body() dto: ContractDto): Promise<void> { async create(@Body() dto: ContractDto): Promise<void> {
await this.contractService.create({ ...dto, domain }); await this.contractService.create(dto);
} }
@Put(':id') @Put(':id')
@ -71,10 +70,8 @@ export class ContractController {
@Put('unlink-attachments/:id') @Put('unlink-attachments/:id')
@ApiOperation({ summary: '附件解除关联' }) @ApiOperation({ summary: '附件解除关联' })
@Perm(permissions.UPDATE) @Perm(permissions.UPDATE)
async unlinkAttachments( async unlinkAttachments(@IdParam() id: number, @Body() {fileIds}: ContractUpdateDto): Promise<void> {
@IdParam() id: number,
@Body() { fileIds }: ContractUpdateDto
): Promise<void> {
await this.contractService.unlinkAttachments(id, fileIds); await this.contractService.unlinkAttachments(id, fileIds);
} }
} }

View File

@ -15,9 +15,8 @@ import {
import { PagerDto } from '~/common/dto/pager.dto'; import { PagerDto } from '~/common/dto/pager.dto';
import { Storage } from '../tools/storage/storage.entity'; import { Storage } from '../tools/storage/storage.entity';
import { ContractStatusEnum } from '~/constants/enum'; import { ContractStatusEnum } from '~/constants/enum';
import { DomainType, SkDomain } from '~/common/decorators/domain.decorator';
export class ContractDto extends DomainType { export class ContractDto {
@ApiProperty({ description: '合同编号' }) @ApiProperty({ description: '合同编号' })
@Matches(/^[a-z0-9A-Z]+$/, { message: '合同编号只能包含字母和数字' }) @Matches(/^[a-z0-9A-Z]+$/, { message: '合同编号只能包含字母和数字' })
@IsString() @IsString()
@ -66,7 +65,5 @@ export class ContractUpdateDto extends PartialType(ContractDto) {
} }
export class ContractQueryDto extends IntersectionType( export class ContractQueryDto extends IntersectionType(
PagerDto<ContractDto>, PagerDto<ContractDto>,
PartialType(ContractDto), PartialType(ContractDto)
DomainType
) {} ) {}

View File

@ -2,7 +2,6 @@ import { ApiHideProperty, ApiProperty } from '@nestjs/swagger';
import { Column, Entity, JoinTable, ManyToMany, Relation } from 'typeorm'; import { Column, Entity, JoinTable, ManyToMany, Relation } from 'typeorm';
import { CommonEntity } from '~/common/entity/common.entity'; import { CommonEntity } from '~/common/entity/common.entity';
import { Storage } from '../tools/storage/storage.entity'; import { Storage } from '../tools/storage/storage.entity';
import { SkDomain } from '~/common/decorators/domain.decorator';
@Entity({ name: 'contract' }) @Entity({ name: 'contract' })
export class ContractEntity extends CommonEntity { export class ContractEntity extends CommonEntity {
@ -48,10 +47,6 @@ export class ContractEntity extends CommonEntity {
@ApiProperty({ description: '删除状态0未删除1已删除' }) @ApiProperty({ description: '删除状态0未删除1已删除' })
isDelete: number; isDelete: number;
@Column({ type: 'int', default: 1, comment: '所属域' })
@ApiProperty({ description: '所属域' })
domain: SkDomain;
@ManyToMany(() => Storage, storage => storage.contracts) @ManyToMany(() => Storage, storage => storage.contracts)
@JoinTable({ @JoinTable({
name: 'contract_storage', name: 'contract_storage',

View File

@ -10,7 +10,6 @@ import { Storage } from '../tools/storage/storage.entity';
import { BusinessException } from '~/common/exceptions/biz.exception'; import { BusinessException } from '~/common/exceptions/biz.exception';
import { ErrorEnum } from '~/constants/error-code.constant'; import { ErrorEnum } from '~/constants/error-code.constant';
import { fieldSearch } from '~/shared/database/field-search'; import { fieldSearch } from '~/shared/database/field-search';
import { SkDomain } from '~/common/decorators/domain.decorator';
@Injectable() @Injectable()
export class ContractService { export class ContractService {

View File

@ -1,67 +0,0 @@
import {
Body,
Controller,
Get,
Query,
Put,
Delete,
Post,
BadRequestException
} from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { Perm, definePermission } from '../auth/decorators/permission.decorator';
import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator';
import { DomainService } from './domain.service';
import { ApiResult } from '~/common/decorators/api-result.decorator';
import { DomainEntity } from './domain.entity';
import { DomainDto, DomainQueryDto, DomainUpdateDto } from './domain.dto';
import { IdParam } from '~/common/decorators/id-param.decorator';
export const permissions = definePermission('app:domain', {
CREATE: 'create',
READ: 'read',
UPDATE: 'update',
DELETE: 'delete'
} as const);
@ApiTags('Domain - 域')
@ApiSecurityAuth()
@Controller('domain')
export class DomainController {
constructor(private domainService: DomainService) {}
@Get()
@ApiOperation({ summary: '获取域列表' })
@ApiResult({ type: [DomainEntity], isPage: true })
async list(@Query() dto: DomainQueryDto) {
return this.domainService.findAll(dto);
}
@Get(':id')
@ApiOperation({ summary: '获取域信息' })
@ApiResult({ type: DomainDto })
@Perm(permissions.READ)
async info(@IdParam() id: number) {
return this.domainService.info(id);
}
@Post()
@ApiOperation({ summary: '新增域' })
@Perm(permissions.CREATE)
async create(@Body() dto: DomainDto): Promise<void> {
await this.domainService.create(dto);
}
@Put(':id')
@ApiOperation({ summary: '更新域' })
@Perm(permissions.UPDATE)
async update(@IdParam() id: number, @Body() dto: DomainUpdateDto): Promise<void> {
await this.domainService.update(id, dto);
}
@Delete(':id')
@ApiOperation({ summary: '删除域' })
@Perm(permissions.DELETE)
async delete(@IdParam() id: number): Promise<void> {
await this.domainService.delete(id);
}
}

View File

@ -1,12 +0,0 @@
import { ApiProperty, IntersectionType, PartialType } from '@nestjs/swagger';
import { IsString } from 'class-validator';
import { PagerDto } from '~/common/dto/pager.dto';
export class DomainDto {
@ApiProperty({ description: '域标题' })
@IsString()
title: string;
}
export class DomainUpdateDto extends PartialType(DomainDto) {}
export class DomainQueryDto extends IntersectionType(PagerDto<DomainDto>, PartialType(DomainDto)) {}

View File

@ -1,15 +0,0 @@
import { ApiHideProperty, ApiProperty } from '@nestjs/swagger';
import { Column, Entity, JoinTable, ManyToMany, Relation } from 'typeorm';
import { CommonEntity } from '~/common/entity/common.entity';
import { Storage } from '../tools/storage/storage.entity';
@Entity({ name: 'domain' })
export class DomainEntity extends CommonEntity {
@Column({ name: 'title', type: 'varchar', length: 255, comment: '域标题' })
@ApiProperty({ description: '域标题' })
title: string;
@Column({ name: 'is_delete', type: 'tinyint', default: 0, comment: '是否删除' })
@ApiProperty({ description: '删除状态0未删除1已删除' })
isDelete: number;
}

View File

@ -1,12 +0,0 @@
import { Module } from '@nestjs/common';
import { DomainController } from './domain.controller';
import { DomainService } from './domain.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { DomainEntity } from './domain.entity';
@Module({
imports: [TypeOrmModule.forFeature([DomainEntity])],
controllers: [DomainController],
providers: [DomainService]
})
export class DomainModule {}

View File

@ -1,95 +0,0 @@
import { Injectable } from '@nestjs/common';
import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';
import { DomainEntity } from './domain.entity';
import { EntityManager, Like, Not, Repository } from 'typeorm';
import { DomainDto, DomainQueryDto, DomainUpdateDto } from './domain.dto';
import { Pagination } from '~/helper/paginate/pagination';
import { isNumber } from 'lodash';
import { paginate } from '~/helper/paginate';
import { Storage } from '../tools/storage/storage.entity';
import { BusinessException } from '~/common/exceptions/biz.exception';
import { ErrorEnum } from '~/constants/error-code.constant';
import { fieldSearch } from '~/shared/database/field-search';
@Injectable()
export class DomainService {
constructor(
@InjectEntityManager() private entityManager: EntityManager,
@InjectRepository(DomainEntity)
private domainRepository: Repository<DomainEntity>
) {}
/**
*
*/
async findAll({ page, pageSize, ...fields }: DomainQueryDto): Promise<Pagination<DomainEntity>> {
const queryBuilder = this.domainRepository
.createQueryBuilder('domain')
.where(fieldSearch(fields))
.andWhere('domain.isDelete = 0');
return paginate<DomainEntity>(queryBuilder, {
page,
pageSize
});
}
/**
*
*/
async create({ title, ...ext }: DomainDto): Promise<void> {
if (await this.checkIsDomainExsit(title)) {
throw new BusinessException(ErrorEnum.DOMAIN_TITLE_DUPLICATE);
}
await this.domainRepository.insert(this.domainRepository.create({ title, ...ext }));
}
/**
*
*/
async update(id: number, { title, ...ext }: Partial<DomainUpdateDto>): Promise<void> {
await this.entityManager.transaction(async manager => {
if (title && (await this.checkIsDomainExsit(title, id))) {
throw new BusinessException(ErrorEnum.CONTRACT_NUMBER_EXIST);
}
await manager.update(DomainEntity, id, {
...ext,
title
});
});
}
/**
*
* @param title
*/
async checkIsDomainExsit(title: string, id?: number): Promise<Boolean> {
return !!(await this.domainRepository.findOne({
where: {
title: title,
id: Not(id)
}
}));
}
/**
*
*/
async delete(id: number): Promise<void> {
// 域比较重要,做逻辑删除
await this.domainRepository.update(id, { isDelete: 1 });
}
/**
*
*/
async info(id: number) {
const info = await this.domainRepository
.createQueryBuilder('domain')
.where({
id
})
.andWhere('domain.isDelete = 0')
.getOne();
return info;
}
}

View File

@ -1,4 +1,4 @@
import { Body, Controller, Delete, Get, Post, Put, Query, Res } from '@nestjs/common'; import { Body, Controller, Delete, Get, Post, Put, Query } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { ApiResult } from '~/common/decorators/api-result.decorator'; import { ApiResult } from '~/common/decorators/api-result.decorator';
import { IdParam } from '~/common/decorators/id-param.decorator'; import { IdParam } from '~/common/decorators/id-param.decorator';
@ -9,44 +9,28 @@ import { definePermission, Perm } from '~/modules/auth/decorators/permission.dec
import { import {
MaterialsInOutQueryDto, MaterialsInOutQueryDto,
MaterialsInOutDto, MaterialsInOutDto,
MaterialsInOutUpdateDto, MaterialsInOutUpdateDto
MaterialsInOutExportDto
} from './materials_in_out.dto'; } from './materials_in_out.dto';
import { Domain, DomainType, SkDomain } from '~/common/decorators/domain.decorator';
import { FastifyReply } from 'fastify';
export const permissions = definePermission('materials_inventory:history_in_out', { export const permissions = definePermission('materials_inventory:history_in_out', {
LIST: 'list', LIST: 'list',
CREATE: 'create', CREATE: 'create',
READ: 'read', READ: 'read',
UPDATE: 'update', UPDATE: 'update',
DELETE: 'delete', DELETE: 'delete'
EXPORT: 'export'
} as const); } as const);
@ApiTags('Materials In Out History - 原材料出入库记录') @ApiTags('Materials In Out History - 原材料出入库记录')
@ApiSecurityAuth() @ApiSecurityAuth()
@Controller('materials-in-out') @Controller('materials-in-out')
export class MaterialsInOutController { export class MaterialsInOutController {
constructor(private materialsInOutService: MaterialsInOutService) { } constructor(private materialsInOutService: MaterialsInOutService) {}
@Get('export')
@ApiOperation({ summary: '导出原材料盘点表' })
@Perm(permissions.EXPORT)
async exportMaterialsInventoryCheck(
@Domain() domain: SkDomain,
@Query() dto: MaterialsInOutExportDto,
@Res() res: FastifyReply
): Promise<void> {
await this.materialsInOutService.exportMaterialsInventoryCheck({ ...dto, domain }, res);
}
@Get() @Get()
@ApiOperation({ summary: '获取原材料出入库记录列表' }) @ApiOperation({ summary: '获取原材料出入库记录列表' })
@ApiResult({ type: [MaterialsInOutEntity], isPage: true }) @ApiResult({ type: [MaterialsInOutEntity], isPage: true })
@Perm(permissions.LIST) @Perm(permissions.LIST)
async list(@Domain() domain: SkDomain, @Query() dto: MaterialsInOutQueryDto) { async list(@Query() dto: MaterialsInOutQueryDto) {
return this.materialsInOutService.findAll({ ...dto, domain }); return this.materialsInOutService.findAll(dto);
} }
@Get(':id') @Get(':id')
@ -60,8 +44,8 @@ export class MaterialsInOutController {
@Post() @Post()
@ApiOperation({ summary: '新增原材料出入库记录' }) @ApiOperation({ summary: '新增原材料出入库记录' })
@Perm(permissions.CREATE) @Perm(permissions.CREATE)
async create(@Domain() domain: SkDomain, @Body() dto: MaterialsInOutDto): Promise<number> { async create(@Body() dto: MaterialsInOutDto): Promise<number> {
return this.materialsInOutService.create({ ...dto, domain }); return this.materialsInOutService.create(dto);
} }
@Put(':id') @Put(':id')

View File

@ -17,13 +17,12 @@ import {
isNumber isNumber
} from 'class-validator'; } from 'class-validator';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { DomainType } from '~/common/decorators/domain.decorator';
import { PagerDto } from '~/common/dto/pager.dto'; import { PagerDto } from '~/common/dto/pager.dto';
import { MaterialsInOrOutEnum } from '~/constants/enum'; import { MaterialsInOrOutEnum } from '~/constants/enum';
import { Storage } from '~/modules/tools/storage/storage.entity'; import { Storage } from '~/modules/tools/storage/storage.entity';
import { formatToDate } from '~/utils'; import { formatToDate } from '~/utils';
export class MaterialsInOutDto extends DomainType { export class MaterialsInOutDto {
@IsOptional() @IsOptional()
@IsNumber() @IsNumber()
@ApiProperty({ description: '项目Id' }) @ApiProperty({ description: '项目Id' })
@ -54,9 +53,6 @@ export class MaterialsInOutDto extends DomainType {
inOrOut: MaterialsInOrOutEnum; inOrOut: MaterialsInOrOutEnum;
@ApiProperty({ description: '时间' }) @ApiProperty({ description: '时间' })
@Transform(params => {
return params.value ? new Date(params.value) : null;
})
@IsOptional() @IsOptional()
time: Date; time: Date;
@ -101,10 +97,7 @@ export class MaterialsInOutUpdateDto extends PartialType(MaterialsInOutDto) {
@IsArray() @IsArray()
fileIds: number[]; fileIds: number[];
} }
export class MaterialsInOutQueryDto extends IntersectionType( export class MaterialsInOutQueryDto extends PagerDto<MaterialsInOutQueryDto> {
PagerDto<MaterialsInOutQueryDto>,
DomainType
) {
@ApiProperty({ description: '出入库时间YYYY-MM-DD' }) @ApiProperty({ description: '出入库时间YYYY-MM-DD' })
@IsOptional() @IsOptional()
// @IsString() // @IsString()
@ -159,41 +152,3 @@ export class MaterialsInOutQueryDto extends IntersectionType(
@ApiProperty({ description: '是否是用于创建出库记录' }) @ApiProperty({ description: '是否是用于创建出库记录' })
isCreateOut?: boolean; isCreateOut?: boolean;
} }
export class MaterialsInOutExportDto extends IntersectionType(
DomainType
) {
@ApiProperty({ description: '导出时间YYYY-MM-DD' })
@IsOptional()
@IsArray()
@Transform(params => {
// 开始和结束时间用的是一月的开始和一月的结束的时分秒
const date = params.value;
return [
date ? `${date[0]} 00:00:00` : null,
date ? `${date[1]} 23:59:59` : null
];
})
time?: string[];
@ApiProperty({ description: '导出文件名' })
@IsOptional()
@IsString()
filename?: string
@ApiProperty({ description: '入库或出库 0:入库 1:出库' })
@IsOptional()
@IsEnum(MaterialsInOrOutEnum)
inOrOut?: MaterialsInOrOutEnum;
@ApiProperty({ description: '产品名称' })
@IsOptional()
@IsString()
product?: string;
@ApiProperty({ description: '经办人' })
@IsOptional()
@IsString()
agent?: string;
}

View File

@ -20,7 +20,6 @@ import { ProjectEntity } from '~/modules/project/project.entity';
import { ParamConfigEntity } from '~/modules/system/param-config/param-config.entity'; import { ParamConfigEntity } from '~/modules/system/param-config/param-config.entity';
import { Storage } from '~/modules/tools/storage/storage.entity'; import { Storage } from '~/modules/tools/storage/storage.entity';
import { MaterialsInventoryEntity } from '../materials_inventory.entity'; import { MaterialsInventoryEntity } from '../materials_inventory.entity';
import { SkDomain } from '~/common/decorators/domain.decorator';
@Entity({ name: 'materials_in_out' }) @Entity({ name: 'materials_in_out' })
export class MaterialsInOutEntity extends CommonEntity { export class MaterialsInOutEntity extends CommonEntity {
@Column({ @Column({
@ -130,10 +129,6 @@ export class MaterialsInOutEntity extends CommonEntity {
@JoinColumn({ name: 'product_id' }) @JoinColumn({ name: 'product_id' })
product: ProductEntity; product: ProductEntity;
@Column({ type: 'int', default: 1, comment: '所属域' })
@ApiProperty({ description: '所属域' })
domain: SkDomain;
@ManyToMany(() => Storage, storage => storage.materialsInOuts) @ManyToMany(() => Storage, storage => storage.materialsInOuts)
@JoinTable({ @JoinTable({
name: 'materials_in_out_storage', name: 'materials_in_out_storage',

View File

@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm'; import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';
import { Between, EntityManager, In, Repository } from 'typeorm'; import { EntityManager, In, Repository } from 'typeorm';
import { Pagination } from '~/helper/paginate/pagination'; import { Pagination } from '~/helper/paginate/pagination';
import { BusinessException } from '~/common/exceptions/biz.exception'; import { BusinessException } from '~/common/exceptions/biz.exception';
import { ErrorEnum } from '~/constants/error-code.constant'; import { ErrorEnum } from '~/constants/error-code.constant';
@ -10,8 +10,7 @@ import { Storage } from '~/modules/tools/storage/storage.entity';
import { import {
MaterialsInOutQueryDto, MaterialsInOutQueryDto,
MaterialsInOutDto, MaterialsInOutDto,
MaterialsInOutUpdateDto, MaterialsInOutUpdateDto
MaterialsInOutExportDto
} from './materials_in_out.dto'; } from './materials_in_out.dto';
import { MaterialsInOutEntity } from './materials_in_out.entity'; import { MaterialsInOutEntity } from './materials_in_out.entity';
import { fieldSearch } from '~/shared/database/field-search'; import { fieldSearch } from '~/shared/database/field-search';
@ -20,9 +19,7 @@ import { MaterialsInOrOutEnum, ParamConfigEnum } from '~/constants/enum';
import { MaterialsInventoryEntity } from '../materials_inventory.entity'; import { MaterialsInventoryEntity } from '../materials_inventory.entity';
import { MaterialsInventoryService } from '../materials_inventory.service'; import { MaterialsInventoryService } from '../materials_inventory.service';
import { isDefined } from 'class-validator'; import { isDefined } from 'class-validator';
import { FastifyReply } from 'fastify';
import * as ExcelJS from 'exceljs';
import dayjs from 'dayjs';
@Injectable() @Injectable()
export class MaterialsInOutService { export class MaterialsInOutService {
constructor( constructor(
@ -34,119 +31,7 @@ export class MaterialsInOutService {
@InjectRepository(ParamConfigEntity) @InjectRepository(ParamConfigEntity)
private paramConfigRepository: Repository<ParamConfigEntity>, private paramConfigRepository: Repository<ParamConfigEntity>,
private materialsInventoryService: MaterialsInventoryService private materialsInventoryService: MaterialsInventoryService
) { } ) {}
/**
*
*/
async exportMaterialsInventoryCheck(
{ time, domain, filename, ...ext }: MaterialsInOutExportDto,
res: FastifyReply
): Promise<void> {
const ROW_HEIGHT = 20;
const HEADER_FONT_SIZE = 18;
// 生成数据
const sqb = this.buildSearchQuery()
.where(fieldSearch(ext))
.andWhere({
time: Between(time[0], time[1])
})
.andWhere('materialsInOut.isDelete = 0');
const data = await sqb.addOrderBy('materialsInOut.time', 'DESC').getMany();
const workbook = new ExcelJS.Workbook();
const sheet = workbook.addWorksheet('出入库记录');
sheet.mergeCells('A1:T1');
// 设置标题
sheet.getCell('A1').value = '山东矿机华信智能科技有限公司出入库记录表';
// 设置日期
sheet.mergeCells('A2:C2');
sheet.getCell('A2').value = `日期:${dayjs(time[0]).format('YYYY年M月D日')}-${dayjs(time[1]).format('YYYY年M月D日')}`;
// 设置表头
const headers = [
'出入库单号',
'出入库',
'项目',
'公司名称',
'产品名称',
'规格型号',
'时间',
'单位',
'数量',
'单价',
'金额',
'经办人',
'领料单号',
'备注'
];
sheet.addRow(headers);
for (let index = 0; index < data.length; index++) {
const record = data[index];
sheet.addRow([
`${record.inventoryInOutNumber}`,
record.project?.name || '',
record.inOrOut === MaterialsInOrOutEnum.In ? '入库' : "出库",
record.product?.company?.name || '',
record.product?.name || '',
record.product?.productSpecification || '',
`${dayjs(record.time).format('YYYY-MM-DD HH:mm')}`,
record.product.unit.label || '',
record.quantity,
parseFloat(`${record.unitPrice || 0}`),
parseFloat(`${record.amount || 0}`),
`${record?.agent || ''}`,
record?.issuanceNumber || '',
record?.remark || ''
]);
}
// 固定信息样式设定
sheet.eachRow((row, index) => {
if (index >= 3) {
row.alignment = { vertical: 'middle', horizontal: 'center' };
row.height = ROW_HEIGHT;
row.eachCell(cell => {
cell.border = {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' }
};
});
}
});
sheet.columns.forEach((column, index: number) => {
let maxColumnLength = 0;
const autoWidth = ['B', 'C', 'S', 'U'];
if (String.fromCharCode(65 + index) === 'B') maxColumnLength = 20;
if (autoWidth.includes(String.fromCharCode(65 + index))) {
column.eachCell({ includeEmpty: true }, (cell, rowIndex) => {
if (rowIndex >= 5) {
const columnLength = `${cell.value || ''}`.length;
if (columnLength > maxColumnLength) {
maxColumnLength = columnLength;
}
}
});
column.width = maxColumnLength < 12 ? 12 : maxColumnLength; // Minimum width of 10
} else {
column.width = 12;
}
});
//读取buffer进行传输
const buffer = await workbook.xlsx.writeBuffer();
res
.header('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
.header(
'Content-Disposition',
`attachment; filename="${filename}.xls"`
)
.send(buffer);
}
/** /**
* *
*/ */
@ -219,8 +104,7 @@ export class MaterialsInOutService {
position, position,
unitPrice, unitPrice,
quantity, quantity,
productId, productId
domain
} = dto; } = dto;
inventoryInOutNumber = await this.generateInventoryInOutNumber(inOrOut); inventoryInOutNumber = await this.generateInventoryInOutNumber(inOrOut);
let newRecordId; let newRecordId;
@ -231,7 +115,7 @@ export class MaterialsInOutService {
Object.is(inOrOut, MaterialsInOrOutEnum.In) Object.is(inOrOut, MaterialsInOrOutEnum.In)
? this.materialsInventoryService.inInventory.bind(this.materialsInventoryService) ? this.materialsInventoryService.inInventory.bind(this.materialsInventoryService)
: this.materialsInventoryService.outInventory.bind(this.materialsInventoryService) : this.materialsInventoryService.outInventory.bind(this.materialsInventoryService)
)({ productId, quantity, unitPrice, projectId, inventoryId, position }, manager, domain); )({ productId, quantity, unitPrice, projectId, inventoryId, position }, manager);
// 2.生成出入库记录 // 2.生成出入库记录
const { id } = await manager.save(MaterialsInOutEntity, { const { id } = await manager.save(MaterialsInOutEntity, {
...this.materialsInOutRepository.create({ ...dto, inventoryId: inventoryEntity?.id }), ...this.materialsInOutRepository.create({ ...dto, inventoryId: inventoryEntity?.id }),

View File

@ -13,7 +13,6 @@ import { MaterialsInventoryService } from './materials_inventory.service';
import { MaterialsInventoryEntity } from './materials_inventory.entity'; import { MaterialsInventoryEntity } from './materials_inventory.entity';
import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator'; import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator';
import { FastifyReply } from 'fastify'; import { FastifyReply } from 'fastify';
import { Domain, DomainType, SkDomain } from '~/common/decorators/domain.decorator';
export const permissions = definePermission('app:materials_inventory', { export const permissions = definePermission('app:materials_inventory', {
LIST: 'list', LIST: 'list',
@ -34,19 +33,18 @@ export class MaterialsInventoryController {
@ApiOperation({ summary: '导出原材料盘点表' }) @ApiOperation({ summary: '导出原材料盘点表' })
@Perm(permissions.EXPORT) @Perm(permissions.EXPORT)
async exportMaterialsInventoryCheck( async exportMaterialsInventoryCheck(
@Domain() domain: SkDomain,
@Query() dto: MaterialsInventoryExportDto, @Query() dto: MaterialsInventoryExportDto,
@Res() res: FastifyReply @Res() res: FastifyReply
): Promise<void> { ): Promise<void> {
await this.miService.exportMaterialsInventoryCheck({ ...dto, domain }, res); await this.miService.exportMaterialsInventoryCheck(dto, res);
} }
@Get() @Get()
@ApiOperation({ summary: '获取原材料库存列表' }) @ApiOperation({ summary: '获取原材料库存列表' })
@ApiResult({ type: [MaterialsInventoryEntity], isPage: true }) @ApiResult({ type: [MaterialsInventoryEntity], isPage: true })
@Perm(permissions.LIST) @Perm(permissions.LIST)
async list(@Domain() domain: SkDomain, @Query() dto: MaterialsInventoryQueryDto) { async list(@Query() dto: MaterialsInventoryQueryDto) {
return this.miService.findAll({ ...dto, domain }); return this.miService.findAll(dto);
} }
@Get(':id') @Get(':id')

View File

@ -18,15 +18,13 @@ import { Transform } from 'class-transformer';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { formatToDate } from '~/utils'; import { formatToDate } from '~/utils';
import { HasInventoryStatusEnum } from '~/constants/enum'; import { HasInventoryStatusEnum } from '~/constants/enum';
import { DomainType } from '~/common/decorators/domain.decorator';
export class MaterialsInventoryDto extends DomainType {} export class MaterialsInventoryDto {}
export class MaterialsInventoryUpdateDto extends PartialType(MaterialsInventoryDto) {} export class MaterialsInventoryUpdateDto extends PartialType(MaterialsInventoryDto) {}
export class MaterialsInventoryQueryDto extends IntersectionType( export class MaterialsInventoryQueryDto extends IntersectionType(
PagerDto<MaterialsInventoryDto>, PagerDto<MaterialsInventoryDto>,
PartialType(MaterialsInventoryDto), PartialType(MaterialsInventoryDto)
DomainType
) { ) {
@ApiProperty({ description: '产品名' }) @ApiProperty({ description: '产品名' })
@IsOptional() @IsOptional()
@ -48,7 +46,7 @@ export class MaterialsInventoryQueryDto extends IntersectionType(
@IsNumber() @IsNumber()
projectId: number; projectId: number;
} }
export class MaterialsInventoryExportDto extends DomainType { export class MaterialsInventoryExportDto {
@ApiProperty({ description: '项目' }) @ApiProperty({ description: '项目' })
@IsOptional() @IsOptional()
@IsNumber() @IsNumber()
@ -61,14 +59,9 @@ export class MaterialsInventoryExportDto extends DomainType {
// 开始和结束时间用的是一月的开始和一月的结束的时分秒 // 开始和结束时间用的是一月的开始和一月的结束的时分秒
const date = params.value; const date = params.value;
return [ return [
date ? `${date[0]} 00:00:00` : null, date ? `${formatToDate(dayjs(date).startOf('month'))} 00:00:00` : null,
date ? `${date[1]} 23:59:59` : null date ? `${formatToDate(dayjs(date).endOf('month'))} 23:59:59` : null
]; ];
}) })
time?: string[]; time?: string[];
@ApiProperty({ description: '文件名' })
@IsOptional()
@IsString()
filename: string;
} }

View File

@ -13,7 +13,6 @@ import { CommonEntity } from '~/common/entity/common.entity';
import { ProductEntity } from '../product/product.entity'; import { ProductEntity } from '../product/product.entity';
import { ProjectEntity } from '../project/project.entity'; import { ProjectEntity } from '../project/project.entity';
import { MaterialsInOutEntity } from './in_out/materials_in_out.entity'; import { MaterialsInOutEntity } from './in_out/materials_in_out.entity';
import { SkDomain } from '~/common/decorators/domain.decorator';
@Entity({ name: 'materials_inventory' }) @Entity({ name: 'materials_inventory' })
export class MaterialsInventoryEntity extends CommonEntity { export class MaterialsInventoryEntity extends CommonEntity {
@ -71,10 +70,6 @@ export class MaterialsInventoryEntity extends CommonEntity {
@ApiProperty({ description: '删除状态0未删除1已删除' }) @ApiProperty({ description: '删除状态0未删除1已删除' })
isDelete: number; isDelete: number;
@Column({ type: 'int', default: 1, comment: '所属域' })
@ApiProperty({ description: '所属域' })
domain: SkDomain;
@ManyToOne(() => ProjectEntity) @ManyToOne(() => ProjectEntity)
@JoinColumn({ name: 'project_id' }) @JoinColumn({ name: 'project_id' })
project: ProjectEntity; project: ProjectEntity;

View File

@ -10,11 +10,10 @@ import { MaterialsInOutEntity } from './in_out/materials_in_out.entity';
import { ParamConfigModule } from '../system/param-config/param-config.module'; import { ParamConfigModule } from '../system/param-config/param-config.module';
import { ProjectModule } from '../project/project.module'; import { ProjectModule } from '../project/project.module';
import { ProjectEntity } from '../project/project.entity'; import { ProjectEntity } from '../project/project.entity';
import {BabyRecordEntity} from "~/modules/baby_record/baby_record.entity";
@Module({ @Module({
imports: [ imports: [
TypeOrmModule.forFeature([MaterialsInventoryEntity, MaterialsInOutEntity,ProjectEntity,BabyRecordEntity]), TypeOrmModule.forFeature([MaterialsInventoryEntity, MaterialsInOutEntity,ProjectEntity]),
ParamConfigModule, ParamConfigModule,
StorageModule, StorageModule,
ProjectModule ProjectModule

View File

@ -23,7 +23,6 @@ import { BusinessException } from '~/common/exceptions/biz.exception';
import { ErrorEnum } from '~/constants/error-code.constant'; import { ErrorEnum } from '~/constants/error-code.constant';
import { ParamConfigEntity } from '../system/param-config/param-config.entity'; import { ParamConfigEntity } from '../system/param-config/param-config.entity';
import { isDefined } from 'class-validator'; import { isDefined } from 'class-validator';
import { DomainType } from '~/common/decorators/domain.decorator';
@Injectable() @Injectable()
export class MaterialsInventoryService { export class MaterialsInventoryService {
constructor( constructor(
@ -36,13 +35,13 @@ export class MaterialsInventoryService {
private projectRepository: Repository<ProjectEntity>, private projectRepository: Repository<ProjectEntity>,
@InjectRepository(ParamConfigEntity) @InjectRepository(ParamConfigEntity)
private paramConfigRepository: Repository<ParamConfigEntity> private paramConfigRepository: Repository<ParamConfigEntity>
) { } ) {}
/** /**
* *
*/ */
async exportMaterialsInventoryCheck( async exportMaterialsInventoryCheck(
{ time, projectId, domain }: MaterialsInventoryExportDto, { time, projectId }: MaterialsInventoryExportDto,
res: FastifyReply res: FastifyReply
): Promise<void> { ): Promise<void> {
const ROW_HEIGHT = 20; const ROW_HEIGHT = 20;
@ -56,8 +55,7 @@ export class MaterialsInventoryService {
const inventoriesInProjects = await this.materialsInventoryRepository.find({ const inventoriesInProjects = await this.materialsInventoryRepository.find({
where: { where: {
...(projects?.length ? { projectId: In(projects.map(item => item.id)) } : null) ...(projects?.length ? { projectId: In(projects.map(item => item.id)) } : null)
}, }
relations: ['product', 'product.company', 'product.unit']
}); });
// 生成数据 // 生成数据
@ -88,7 +86,7 @@ export class MaterialsInventoryService {
const data = await sqb.addOrderBy('mio.time', 'DESC').getMany(); const data = await sqb.addOrderBy('mio.time', 'DESC').getMany();
if (!projectId) { if (!projectId) {
projects = uniqBy( projects = uniqBy(
data.filter(item => item.inOrOut === MaterialsInOrOutEnum.Out).map(item => item.project), data.map(item => item.project),
'id' 'id'
); );
} }
@ -97,9 +95,7 @@ export class MaterialsInventoryService {
const currentProjectInventories = inventoriesInProjects.filter(({ projectId }) => const currentProjectInventories = inventoriesInProjects.filter(({ projectId }) =>
Object.is(projectId, project.id) Object.is(projectId, project.id)
); );
const currentProjectData = data.filter( const currentProjectData = data.filter(item => item.projectId === project.id);
item => item.projectId === project.id || item.inOrOut === MaterialsInOrOutEnum.Out
);
const currentMonthProjectData = currentProjectData.filter(item => { const currentMonthProjectData = currentProjectData.filter(item => {
return ( return (
dayjs(item.time).isAfter(dayjs(time[0])) && dayjs(item.time).isBefore(dayjs(time[1])) dayjs(item.time).isAfter(dayjs(time[0])) && dayjs(item.time).isBefore(dayjs(time[1]))
@ -114,7 +110,7 @@ export class MaterialsInventoryService {
sheet.getCell('A2').value = `日期:${dayjs(time[0]).format('YYYY年M月')}`; sheet.getCell('A2').value = `日期:${dayjs(time[0]).format('YYYY年M月')}`;
// 设置表头 // 设置表头
const headers = [ const headers = [
'号', '出入库单号',
'公司名称', '公司名称',
'产品名称', '产品名称',
'单位', '单位',
@ -132,6 +128,8 @@ export class MaterialsInventoryService {
'结存数量', '结存数量',
'单价', '单价',
'金额', '金额',
'经办人',
'领料单号',
'备注' '备注'
]; ];
sheet.addRow(headers); sheet.addRow(headers);
@ -154,6 +152,8 @@ export class MaterialsInventoryService {
'', '',
'', '',
'', '',
'',
'',
'' ''
]); ]);
for (let i = 1; i <= 7; i++) { for (let i = 1; i <= 7; i++) {
@ -175,7 +175,7 @@ export class MaterialsInventoryService {
}; };
} }
for (let i = 16; i <= 19; i++) { for (let i = 16; i <= 21; i++) {
sheet.mergeCells(`${String.fromCharCode(64 + i)}3:${String.fromCharCode(64 + i)}4`); sheet.mergeCells(`${String.fromCharCode(64 + i)}3:${String.fromCharCode(64 + i)}4`);
} }
@ -196,40 +196,24 @@ export class MaterialsInventoryService {
} }
}); });
// 根据库存Id分组
const groupedData = groupBy<MaterialsInOutEntity>( const groupedData = groupBy<MaterialsInOutEntity>(
currentMonthProjectData, currentMonthProjectData,
record => record.inventoryId record => `${record.productId}-${record.unitPrice}`
); );
let number = 0; let number = 0;
const groupedInventories = groupBy(currentProjectInventories, item => item.id); const groupedInventories = groupBy(
let orderNo = 0; currentProjectInventories,
item => `${item.projectId}_${item.productId}`
for (const key in groupedInventories) { );
orderNo++; for (const key in groupedData) {
// 目前暂定逻辑出库只有一次或者没有出库。不会对一个入库的记录多次出库,故而用find。---废弃 // 目前暂定逻辑出库只有一次或者没有出库。不会对一个入库的记录多次出库,故而用find。
// 2024.04.16 改成 const inRecord = groupedData[key].find(item => item.inOrOut === MaterialsInOrOutEnum.In);
const inventory = groupedInventories[key][0]; const outRecord = groupedData[key].find(item => item.inOrOut === MaterialsInOrOutEnum.Out);
const outRecords = groupedData[key].filter( const currInventories =
item => item.inOrOut === MaterialsInOrOutEnum.Out groupedInventories[`${inRecord.projectId}_${inRecord.productId}`]?.shift();
const allDataFromMonth = data.filter(
res => res.projectId === inRecord.projectId && res.productId === inRecord.productId
); );
const inRecords = groupedData[key].filter(item => item.inOrOut === MaterialsInOrOutEnum.In);
const outRecordQuantity = outRecords
.map(item => item.quantity)
.reduce((acc, cur) => {
return calcNumber(acc, cur, 'add');
}, 0);
const inRecordQuantity = inRecords
.map(item => item.quantity)
.reduce((acc, cur) => {
return calcNumber(acc, cur, 'add');
}, 0);
// 这里的单价默认入库价格和出库价格一致,所以直接用总数量*入库单价
const outRecordAmount = calcNumber(outRecordQuantity, inventory.unitPrice || 0, 'multiply');
const inRecordAmount = calcNumber(inRecordQuantity, inventory.unitPrice || 0, 'multiply');
const currInventories = groupedInventories[key]?.shift();
const allDataFromMonth = data.filter(res => res.inventoryId == Number(key));
let currentQuantity = 0; let currentQuantity = 0;
let balanceQuantity = 0; let balanceQuantity = 0;
// 月初库存数量 // 月初库存数量
@ -250,33 +234,32 @@ export class MaterialsInventoryService {
// 结存库存数量 // 结存库存数量
balanceQuantity = calcNumber( balanceQuantity = calcNumber(
currentQuantity, currentQuantity,
calcNumber(inRecordQuantity, outRecordQuantity, 'subtract'), calcNumber(inRecord.quantity, outRecord?.quantity || 0, 'subtract'),
'add' 'add'
); );
number++; number++;
sheet.addRow([ sheet.addRow([
`${orderNo}`, `${inRecord.inventoryInOutNumber || ''}`,
inventory.product?.company?.name || '', inRecord.product.company.name || '',
inventory.product?.name || '', inRecord.product.name || '',
inventory.product.unit.label || '', inRecord.product.unit.label || '',
currentQuantity, currentQuantity,
parseFloat(`${inventory.unitPrice || 0}`), parseFloat(`${inRecord.unitPrice || 0}`),
calcNumber(currentQuantity, inventory.unitPrice || 0, 'multiply'), calcNumber(currentQuantity, inRecord.unitPrice || 0, 'multiply'),
// inRecord.time, inRecord.time,
'', inRecord.quantity || 0,
inRecordQuantity, parseFloat(`${inRecord.unitPrice || 0}`),
parseFloat(`${inventory.unitPrice || 0}`), parseFloat(`${inRecord.amount || 0}`),
parseFloat(`${inRecordAmount}`), outRecord?.time || '',
// outRecord?.time || '', outRecord?.quantity || 0,
'', parseFloat(`${outRecord?.unitPrice || 0}`),
outRecordQuantity, parseFloat(`${outRecord?.amount || 0}`),
parseFloat(`${inventory?.unitPrice || 0}`),
parseFloat(`${outRecordAmount}`),
balanceQuantity, balanceQuantity,
parseFloat(`${inventory?.unitPrice || 0}`), parseFloat(`${inRecord?.unitPrice || 0}`),
calcNumber(balanceQuantity, inventory?.unitPrice || 0, 'multiply'), calcNumber(balanceQuantity, inRecord?.unitPrice || 0, 'multiply'),
// `${inRecord?.agent || ''}/${outRecord?.agent || ''}`, `${inRecord?.agent || ''}/${outRecord?.agent || ''}`,
'' outRecord?.issuanceNumber || '',
`${inRecord?.remark || ''}/${outRecord?.remark || ''}`
]); ]);
} }
sheet.getCell('A1').font = { size: HEADER_FONT_SIZE }; sheet.getCell('A1').font = { size: HEADER_FONT_SIZE };
@ -336,8 +319,7 @@ export class MaterialsInventoryService {
product, product,
keyword, keyword,
projectId, projectId,
isHasInventory, isHasInventory
domain
}: MaterialsInventoryQueryDto): Promise<Pagination<MaterialsInventoryEntity>> { }: MaterialsInventoryQueryDto): Promise<Pagination<MaterialsInventoryEntity>> {
const queryBuilder = this.materialsInventoryRepository const queryBuilder = this.materialsInventoryRepository
.createQueryBuilder('materialsInventory') .createQueryBuilder('materialsInventory')
@ -357,8 +339,7 @@ export class MaterialsInventoryService {
'product.productSpecification', 'product.productSpecification',
'product.productNumber' 'product.productNumber'
]) ])
.where(fieldSearch({ domain })) .where('materialsInventory.isDelete = 0');
.andWhere('materialsInventory.isDelete = 0');
if (product) { if (product) {
queryBuilder.andWhere('product.name like :product', { product: `%${product}%` }); queryBuilder.andWhere('product.name like :product', { product: `%${product}%` });
} }
@ -421,8 +402,7 @@ export class MaterialsInventoryService {
unitPrice?: number; unitPrice?: number;
changedUnitPrice?: number; changedUnitPrice?: number;
}, },
manager: EntityManager, manager: EntityManager
domain?: DomainType
): Promise<MaterialsInventoryEntity> { ): Promise<MaterialsInventoryEntity> {
const { const {
projectId, projectId,
@ -435,9 +415,9 @@ export class MaterialsInventoryService {
} = data; } = data;
let searchPayload: any = {}; let searchPayload: any = {};
if (isDefined(inventoryId)) { if (isDefined(inventoryId)) {
searchPayload = { id: inventoryId, domain }; searchPayload = { id: inventoryId };
} else { } else {
searchPayload = { projectId, productId, unitPrice, domain }; searchPayload = { projectId, productId, unitPrice };
} }
const exsitedInventory = await manager.findOne(MaterialsInventoryEntity, { const exsitedInventory = await manager.findOne(MaterialsInventoryEntity, {
where: searchPayload, // 根据项目,产品,价格查出之前的实时库存情况 where: searchPayload, // 根据项目,产品,价格查出之前的实时库存情况
@ -481,7 +461,7 @@ export class MaterialsInventoryService {
quantity: number; quantity: number;
inventoryId?: number; inventoryId?: number;
}, },
manager: EntityManager, manager: EntityManager
): Promise<MaterialsInventoryEntity> { ): Promise<MaterialsInventoryEntity> {
const { quantity: outQuantity, inventoryId } = data; const { quantity: outQuantity, inventoryId } = data;
// 开启悲观行锁,防止脏读和修改 // 开启悲观行锁,防止脏读和修改

View File

@ -16,7 +16,6 @@ import { ApiResult } from '~/common/decorators/api-result.decorator';
import { ProductEntity } from './product.entity'; import { ProductEntity } from './product.entity';
import { ProductDto, ProductQueryDto, ProductUpdateDto } from './product.dto'; import { ProductDto, ProductQueryDto, ProductUpdateDto } from './product.dto';
import { IdParam } from '~/common/decorators/id-param.decorator'; import { IdParam } from '~/common/decorators/id-param.decorator';
import { Domain, SkDomain } from '~/common/decorators/domain.decorator';
export const permissions = definePermission('app:product', { export const permissions = definePermission('app:product', {
LIST: 'list', LIST: 'list',
CREATE: 'create', CREATE: 'create',
@ -35,8 +34,8 @@ export class ProductController {
@ApiOperation({ summary: '获取产品列表' }) @ApiOperation({ summary: '获取产品列表' })
@ApiResult({ type: [ProductEntity], isPage: true }) @ApiResult({ type: [ProductEntity], isPage: true })
@Perm(permissions.LIST) @Perm(permissions.LIST)
async list(@Domain() domain: SkDomain, @Query() dto: ProductQueryDto) { async list(@Query() dto: ProductQueryDto) {
return this.productService.findAll({ ...dto, domain }); return this.productService.findAll(dto);
} }
@Get(':id') @Get(':id')
@ -50,8 +49,8 @@ export class ProductController {
@Post() @Post()
@ApiOperation({ summary: '新增产品' }) @ApiOperation({ summary: '新增产品' })
@Perm(permissions.CREATE) @Perm(permissions.CREATE)
async create(@Domain() domain: SkDomain, @Body() dto: ProductDto): Promise<void> { async create(@Body() dto: ProductDto): Promise<void> {
await this.productService.create({ ...dto, domain }); await this.productService.create(dto);
} }
@Put(':id') @Put(':id')

View File

@ -2,13 +2,13 @@ import { ApiProperty, IntersectionType, PartialType } from '@nestjs/swagger';
import { IsArray, IsNumber, IsOptional, IsString } from 'class-validator'; import { IsArray, IsNumber, IsOptional, IsString } from 'class-validator';
import { PagerDto } from '~/common/dto/pager.dto'; import { PagerDto } from '~/common/dto/pager.dto';
import { Storage } from '../tools/storage/storage.entity'; import { Storage } from '../tools/storage/storage.entity';
import { DomainType } from '~/common/decorators/domain.decorator';
export class ProductDto extends DomainType { export class ProductDto {
@ApiProperty({ description: '产品名称' }) @ApiProperty({ description: '产品名称' })
@IsString() @IsString()
name: string; name: string;
@ApiProperty({ description: '产品规格' }) @ApiProperty({ description: '产品规格' })
@IsOptional() @IsOptional()
@IsString() @IsString()
@ -42,8 +42,7 @@ export class ProductUpdateDto extends PartialType(ProductDto) {
export class ProductQueryDto extends IntersectionType( export class ProductQueryDto extends IntersectionType(
PagerDto<ProductDto>, PagerDto<ProductDto>,
PartialType(ProductDto), PartialType(ProductDto)
DomainType
) { ) {
@ApiProperty({ description: '所属公司名称' }) @ApiProperty({ description: '所属公司名称' })
@IsOptional() @IsOptional()
@ -59,4 +58,5 @@ export class ProductQueryDto extends IntersectionType(
@IsOptional() @IsOptional()
@IsString() @IsString()
keyword?: string; keyword?: string;
} }

View File

@ -15,7 +15,6 @@ import { Storage } from '../tools/storage/storage.entity';
import { CompanyEntity } from '../company/company.entity'; import { CompanyEntity } from '../company/company.entity';
import pinyin from 'pinyin'; import pinyin from 'pinyin';
import { DictItemEntity } from '../system/dict-item/dict-item.entity'; import { DictItemEntity } from '../system/dict-item/dict-item.entity';
import { SkDomain } from '~/common/decorators/domain.decorator';
@Entity({ name: 'product' }) @Entity({ name: 'product' })
export class ProductEntity extends CommonEntity { export class ProductEntity extends CommonEntity {
@Column({ @Column({
@ -72,10 +71,6 @@ export class ProductEntity extends CommonEntity {
@JoinColumn({ name: 'unit_id' }) @JoinColumn({ name: 'unit_id' })
unit: DictItemEntity; unit: DictItemEntity;
@Column({ type: 'int', default: 1, comment: '所属域' })
@ApiProperty({ description: '所属域' })
domain: SkDomain;
@ApiHideProperty() @ApiHideProperty()
@Column({ @Column({
name: 'name_pinyin', name: 'name_pinyin',

View File

@ -16,7 +16,6 @@ import { ApiResult } from '~/common/decorators/api-result.decorator';
import { ProjectEntity } from './project.entity'; import { ProjectEntity } from './project.entity';
import { ProjectDto, ProjectQueryDto, ProjectUpdateDto } from './project.dto'; import { ProjectDto, ProjectQueryDto, ProjectUpdateDto } from './project.dto';
import { IdParam } from '~/common/decorators/id-param.decorator'; import { IdParam } from '~/common/decorators/id-param.decorator';
import { Domain, SkDomain } from '~/common/decorators/domain.decorator';
export const permissions = definePermission('app:project', { export const permissions = definePermission('app:project', {
LIST: 'list', LIST: 'list',
CREATE: 'create', CREATE: 'create',
@ -35,8 +34,8 @@ export class ProjectController {
@ApiOperation({ summary: '分页获取项目列表' }) @ApiOperation({ summary: '分页获取项目列表' })
@ApiResult({ type: [ProjectEntity], isPage: true }) @ApiResult({ type: [ProjectEntity], isPage: true })
@Perm(permissions.LIST) @Perm(permissions.LIST)
async list(@Domain() domain: SkDomain, @Query() dto: ProjectQueryDto) { async list(@Query() dto: ProjectQueryDto) {
return this.projectService.findAll({ ...dto, domain }); return this.projectService.findAll(dto);
} }
@Get(':id') @Get(':id')
@ -50,8 +49,8 @@ export class ProjectController {
@Post() @Post()
@ApiOperation({ summary: '新增项目' }) @ApiOperation({ summary: '新增项目' })
@Perm(permissions.CREATE) @Perm(permissions.CREATE)
async create(@Domain() domain: SkDomain, @Body() dto: ProjectDto): Promise<void> { async create(@Body() dto: ProjectDto): Promise<void> {
await this.projectService.create({ ...dto, domain }); await this.projectService.create(dto);
} }
@Put(':id') @Put(':id')

View File

@ -4,9 +4,8 @@ import { PagerDto } from '~/common/dto/pager.dto';
import { Storage } from '../tools/storage/storage.entity'; import { Storage } from '../tools/storage/storage.entity';
import { IsUnique } from '~/shared/database/constraints/unique.constraint'; import { IsUnique } from '~/shared/database/constraints/unique.constraint';
import { ProjectEntity } from './project.entity'; import { ProjectEntity } from './project.entity';
import { DomainType } from '~/common/decorators/domain.decorator';
export class ProjectDto extends DomainType { export class ProjectDto {
@ApiProperty({ description: '项目名称' }) @ApiProperty({ description: '项目名称' })
@IsUnique(ProjectEntity, { message: '已存在同名项目' }) @IsUnique(ProjectEntity, { message: '已存在同名项目' })
@IsString() @IsString()
@ -32,8 +31,7 @@ export class ComapnyCreateDto extends PartialType(ProjectDto) {
export class ProjectQueryDto extends IntersectionType( export class ProjectQueryDto extends IntersectionType(
PagerDto<ProjectDto>, PagerDto<ProjectDto>,
PartialType(ProjectDto), PartialType(ProjectDto)
DomainType
) { ) {
@ApiProperty({ description: '项目名称' }) @ApiProperty({ description: '项目名称' })
@IsOptional() @IsOptional()

View File

@ -4,7 +4,6 @@ import { CommonEntity } from '~/common/entity/common.entity';
import { Storage } from '../tools/storage/storage.entity'; import { Storage } from '../tools/storage/storage.entity';
import { ProductEntity } from '../product/product.entity'; import { ProductEntity } from '../product/product.entity';
import { MaterialsInOutEntity } from '../materials_inventory/in_out/materials_in_out.entity'; import { MaterialsInOutEntity } from '../materials_inventory/in_out/materials_in_out.entity';
import { SkDomain } from '~/common/decorators/domain.decorator';
/** /**
* *
@ -25,10 +24,6 @@ export class ProjectEntity extends CommonEntity {
@ApiProperty({ description: '删除状态0未删除1已删除' }) @ApiProperty({ description: '删除状态0未删除1已删除' })
isDelete: number; isDelete: number;
@Column({ type: 'int', default: 1, comment: '所属域' })
@ApiProperty({ description: '所属域' })
domain: SkDomain;
@ApiHideProperty() @ApiHideProperty()
@OneToMany(() => MaterialsInOutEntity, product => product.project) @OneToMany(() => MaterialsInOutEntity, product => product.project)
materialsInOuts: Relation<MaterialsInOutEntity[]>; materialsInOuts: Relation<MaterialsInOutEntity[]>;

View File

@ -56,6 +56,11 @@ export class ProjectService {
await manager.update(ProjectEntity, id, { await manager.update(ProjectEntity, id, {
...data ...data
}); });
const project = await this.projectRepository
.createQueryBuilder('project')
.leftJoinAndSelect('project.files', 'files')
.where('project.id = :id', { id })
.getOne();
if (fileIds?.length) { if (fileIds?.length) {
const count = await this.storageRepository const count = await this.storageRepository
.createQueryBuilder('storage') .createQueryBuilder('storage')

View File

@ -1,69 +0,0 @@
import { Body, Controller, Get, Query, Put, Delete, Post } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator';
import { ApiResult } from '~/common/decorators/api-result.decorator';
import { IdParam } from '~/common/decorators/id-param.decorator';
import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator';
import { SaleQuotationComponentDto, SaleQuotationComponentQueryDto, SaleQuotationComponentUpdateDto } from './sale_quotation_component.dto';
import { SaleQuotationComponentService } from './sale_quotation_component.service';
export const permissions = definePermission('sale_quotation:sale_quotation_component', {
LIST: 'list',
CREATE: 'create',
READ: 'read',
UPDATE: 'update',
DELETE: 'delete'
} as const);
@ApiTags('SaleQuotationComponent - 报价配件')
@ApiSecurityAuth()
@Controller('sale_quotation_component')
export class SaleQuotationComponentController {
constructor(private saleQuotationComponentService: SaleQuotationComponentService) {}
@Get()
@ApiOperation({ summary: '分页获取报价配件列表' })
@ApiResult({ type: [SaleQuotationComponentDto], isPage: true })
@Perm(permissions.LIST)
async list(@Query() dto: SaleQuotationComponentQueryDto) {
return this.saleQuotationComponentService.findAll(dto);
}
@Get(':id')
@ApiOperation({ summary: '获取报价配件信息' })
@ApiResult({ type: SaleQuotationComponentDto })
@Perm(permissions.READ)
async info(@IdParam() id: number) {
return this.saleQuotationComponentService.info(id);
}
@Post()
@ApiOperation({ summary: '新增报价配件' })
@Perm(permissions.CREATE)
async create(@Body() dto: SaleQuotationComponentDto): Promise<void> {
await this.saleQuotationComponentService.create(dto);
}
@Put(':id')
@ApiOperation({ summary: '更新报价配件' })
@Perm(permissions.UPDATE)
async update(@IdParam() id: number, @Body() dto: SaleQuotationComponentUpdateDto): Promise<void> {
await this.saleQuotationComponentService.update(id, dto);
}
@Delete(':id')
@ApiOperation({ summary: '删除报价配件' })
@Perm(permissions.DELETE)
async delete(@IdParam() id: number): Promise<void> {
await this.saleQuotationComponentService.delete(id);
}
@Put('unlink-attachments/:id')
@ApiOperation({ summary: '附件解除关联' })
@Perm(permissions.UPDATE)
async unlinkAttachments(
@IdParam() id: number,
@Body() { fileIds }: SaleQuotationComponentUpdateDto
): Promise<void> {
await this.saleQuotationComponentService.unlinkAttachments(id, fileIds);
}
}

View File

@ -1,59 +0,0 @@
import { ApiProperty, IntersectionType, PartialType } from '@nestjs/swagger';
import { IsArray, IsNumber, IsOptional, IsString } from 'class-validator';
import { PagerDto } from '~/common/dto/pager.dto';
import { Storage } from '~/modules/tools/storage/storage.entity';
import { IsUnique } from '~/shared/database/constraints/unique.constraint';
import { SaleQuotationComponentEntity } from './sale_quotation_component.entity';
export class SaleQuotationComponentDto {
@ApiProperty({ description: '报价配件名称' })
@IsString()
name: string;
@ApiProperty({ description: '配件规格' })
@IsOptional()
@IsString()
componentSpecification: string;
@ApiProperty({ description: '配件备注' })
@IsOptional()
@IsString()
remark: string;
@ApiProperty({ description: '单位(字典)' })
@IsOptional()
@IsNumber()
unitId: number;
@ApiProperty({ description: '单价' })
@IsOptional()
@IsNumber()
unitPrice: number;
@ApiProperty({ description: '附件' })
files: Storage[];
}
export class SaleQuotationComponentUpdateDto extends PartialType(SaleQuotationComponentDto) {
@ApiProperty({ description: '附件' })
@IsOptional()
@IsArray()
fileIds: number[];
}
export class ComapnyCreateDto extends PartialType(SaleQuotationComponentDto) {
@ApiProperty({ description: '附件' })
@IsOptional()
@IsArray()
fileIds: number[];
}
export class SaleQuotationComponentQueryDto extends IntersectionType(
PagerDto<SaleQuotationComponentDto>,
PartialType(SaleQuotationComponentDto)
) {
@ApiProperty({ description: '报价配件名称' })
@IsOptional()
@IsString()
name: string;
}

View File

@ -1,85 +0,0 @@
import { ApiHideProperty, ApiProperty } from '@nestjs/swagger';
import {
Column,
Entity,
JoinColumn,
JoinTable,
ManyToMany,
ManyToOne,
OneToMany,
Relation
} from 'typeorm';
import { CommonEntity } from '~/common/entity/common.entity';
import { Storage } from '~/modules/tools/storage/storage.entity';
import { SaleQuotationGroupEntity } from '../group/sale_quotation_group.entity';
import { DictItemEntity } from '~/modules/system/dict-item/dict-item.entity';
/**
*
*/
@Entity({ name: 'sale_quotation_component' })
export class SaleQuotationComponentEntity extends CommonEntity {
@Column({
name: 'name',
type: 'varchar',
length: 255,
comment: '报价配件名称'
})
@ApiProperty({ description: '报价配件名称' })
name: string;
@Column({
name: 'component_specification',
type: 'varchar',
nullable: true,
length: 255,
comment: '产品规格'
})
@ApiProperty({ description: '产品规格', nullable: true })
componentSpecification?: string;
@Column({ name: 'unit_id', type: 'int', comment: '单位(字典)', nullable: true })
@ApiProperty({ description: '单位(字典)' })
unitId: number;
@ManyToOne(() => DictItemEntity)
@JoinColumn({ name: 'unit_id' })
unit: DictItemEntity;
@Column({
name: 'unit_price',
type: 'decimal',
precision: 15,
default: 0,
scale: 10,
comment: '单价'
})
@ApiProperty({ description: '单价' })
unitPrice: number;
@Column({
name: 'remark',
type: 'varchar',
length: 255,
nullable: true,
comment: '备注'
})
@ApiProperty({ description: '产品备注' })
remark: string;
@Column({ name: 'is_delete', type: 'tinyint', default: 0, comment: '是否删除' })
@ApiProperty({ description: '删除状态0未删除1已删除' })
isDelete: number;
@ManyToMany(() => Storage, storage => storage.saleQuotationComponents)
@JoinTable({
name: 'sale_quotation_component_storage',
joinColumn: { name: 'sale_quotation_component_id', referencedColumnName: 'id' },
inverseJoinColumn: { name: 'file_id', referencedColumnName: 'id' }
})
files: Relation<Storage[]>;
@ApiHideProperty()
@ManyToMany(() => SaleQuotationGroupEntity, group => group.components)
groups: Relation<SaleQuotationGroupEntity[]>;
}

View File

@ -1,14 +0,0 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { StorageModule } from '~/modules/tools/storage/storage.module';
import { DatabaseModule } from '~/shared/database/database.module';
import { SaleQuotationComponentService } from './sale_quotation_component.service';
import { SaleQuotationComponentController } from './sale_quotation_component.controller';
import { SaleQuotationComponentEntity } from './sale_quotation_component.entity';
@Module({
imports: [TypeOrmModule.forFeature([SaleQuotationComponentEntity]), StorageModule, DatabaseModule],
controllers: [SaleQuotationComponentController],
providers: [SaleQuotationComponentService]
})
export class SaleQuotationComponentModule {}

View File

@ -1,161 +0,0 @@
import { Injectable } from '@nestjs/common';
import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';
import { EntityManager, Not, Repository } from 'typeorm';
import { Pagination } from '~/helper/paginate/pagination';
import { paginate } from '~/helper/paginate';
import { BusinessException } from '~/common/exceptions/biz.exception';
import { ErrorEnum } from '~/constants/error-code.constant';
import { fieldSearch } from '~/shared/database/field-search';
import { SaleQuotationComponentEntity } from './sale_quotation_component.entity';
import { Storage } from '~/modules/tools/storage/storage.entity';
import {
SaleQuotationComponentDto,
SaleQuotationComponentQueryDto,
SaleQuotationComponentUpdateDto
} from './sale_quotation_component.dto';
@Injectable()
export class SaleQuotationComponentService {
constructor(
@InjectEntityManager() private entityManager: EntityManager,
@InjectRepository(SaleQuotationComponentEntity)
private saleQuotationComponentRepository: Repository<SaleQuotationComponentEntity>,
@InjectRepository(Storage)
private storageRepository: Repository<Storage>
) {}
buildSearchQuery() {
return this.saleQuotationComponentRepository
.createQueryBuilder('saleQuotationComponent')
.leftJoin('saleQuotationComponent.unit', 'unit')
.leftJoin('saleQuotationComponent.files', 'files')
.addSelect(['files.id', 'files.path', 'unit.id', 'unit.label']);
}
/**
*
*/
async findAll({
page,
pageSize,
...fields
}: SaleQuotationComponentQueryDto): Promise<Pagination<SaleQuotationComponentEntity>> {
const queryBuilder = this.buildSearchQuery()
.where(fieldSearch(fields))
.andWhere('saleQuotationComponent.isDelete = 0');
return paginate<SaleQuotationComponentEntity>(queryBuilder, {
page,
pageSize
});
}
/**
*
*/
async create(dto: SaleQuotationComponentDto): Promise<void> {
const { unitPrice, name, componentSpecification } = dto;
const isExist = await this.saleQuotationComponentRepository.exist({
where: {
unitPrice,
name,
componentSpecification
}
});
if (isExist) {
throw new BusinessException(ErrorEnum.SALE_QUOTATION_COMPONENT_DUPLICATED);
}
await this.saleQuotationComponentRepository.insert(dto);
}
/**
*
*/
async update(
id: number,
{ fileIds, ...data }: Partial<SaleQuotationComponentUpdateDto>
): Promise<void> {
await this.entityManager.transaction(async manager => {
const beUpdateEntity = await manager.findOne(SaleQuotationComponentEntity, {
where: {
id
}
});
const { unitPrice, name, componentSpecification } = beUpdateEntity;
const isExist = await this.saleQuotationComponentRepository.exist({
where: {
id: Not(id),
unitPrice,
name,
componentSpecification
}
});
if (isExist) {
throw new BusinessException(ErrorEnum.SALE_QUOTATION_COMPONENT_DUPLICATED);
}
await manager.update(SaleQuotationComponentEntity, id, {
...data
});
if (fileIds?.length) {
const count = await this.storageRepository
.createQueryBuilder('storage')
.where('storage.id in(:fileIds)', { fileIds })
.getCount();
if (count !== fileIds?.length) {
throw new BusinessException(ErrorEnum.STORAGE_NOT_FOUND);
}
// 附件要批量插入
await manager
.createQueryBuilder()
.relation(SaleQuotationComponentEntity, 'files')
.of(id)
.add(fileIds);
}
});
}
/**
*
*/
async delete(id: number): Promise<void> {
// 比较重要,做逻辑删除
await this.saleQuotationComponentRepository.update(id, { isDelete: 1 });
}
/**
*
*/
async info(id: number) {
const info = await this.buildSearchQuery()
.where({
id
})
.andWhere('saleQuotationComponent.isDelete = 0')
.getOne();
return info;
}
/**
*
* @param id ID
* @param fileIds ID
*/
async unlinkAttachments(id: number, fileIds: number[]) {
await this.entityManager.transaction(async manager => {
const saleQuotationComponent = await this.saleQuotationComponentRepository
.createQueryBuilder('saleQuotationComponent')
.leftJoinAndSelect('saleQuotationComponent.files', 'files')
.where('saleQuotationComponent.id = :id', { id })
.getOne();
const linkedFiles = saleQuotationComponent.files
.map(item => item.id)
.filter(item => !fileIds.includes(item));
// 附件要批量更新
await manager
.createQueryBuilder()
.relation(SaleQuotationComponentEntity, 'files')
.of(id)
.addAndRemove(linkedFiles, saleQuotationComponent.files);
});
}
}

View File

@ -1,63 +0,0 @@
import { Body, Controller, Get, Query, Put, Delete, Post } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator';
import { ApiResult } from '~/common/decorators/api-result.decorator';
import { IdParam } from '~/common/decorators/id-param.decorator';
import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator';
import {
SaleQuotationGroupDto,
SaleQuotationGroupQueryDto,
SaleQuotationGroupUpdateDto
} from './sale_quotation_group.dto';
import { SaleQuotationGroupService } from './sale_quotation_group.service';
export const permissions = definePermission('sale_quotation:sale_quotation_group', {
LIST: 'list',
CREATE: 'create',
READ: 'read',
UPDATE: 'update',
DELETE: 'delete'
} as const);
@ApiTags('SaleQuotationGroup - 报价分组')
@ApiSecurityAuth()
@Controller('sale_quotation_group')
export class SaleQuotationGroupController {
constructor(private saleQuotationGroupService: SaleQuotationGroupService) {}
@Get()
@ApiOperation({ summary: '分页获取报价分组列表' })
@ApiResult({ type: [SaleQuotationGroupDto], isPage: true })
@Perm(permissions.LIST)
async list(@Query() dto: SaleQuotationGroupQueryDto) {
return this.saleQuotationGroupService.findAll(dto);
}
@Get(':id')
@ApiOperation({ summary: '获取报价分组信息' })
@ApiResult({ type: SaleQuotationGroupDto })
@Perm(permissions.READ)
async info(@IdParam() id: number) {
return this.saleQuotationGroupService.info(id);
}
@Post()
@ApiOperation({ summary: '新增报价分组' })
@Perm(permissions.CREATE)
async create(@Body() dto: SaleQuotationGroupDto): Promise<void> {
await this.saleQuotationGroupService.create(dto);
}
@Put(':id')
@ApiOperation({ summary: '更新报价分组' })
@Perm(permissions.UPDATE)
async update(@IdParam() id: number, @Body() dto: SaleQuotationGroupUpdateDto): Promise<void> {
await this.saleQuotationGroupService.update(id, dto);
}
@Delete(':id')
@ApiOperation({ summary: '删除报价分组' })
@Perm(permissions.DELETE)
async delete(@IdParam() id: number): Promise<void> {
await this.saleQuotationGroupService.delete(id);
}
}

View File

@ -1,27 +0,0 @@
import { ApiProperty, IntersectionType, PartialType } from '@nestjs/swagger';
import { IsArray, IsNumber, IsOptional, IsString } from 'class-validator';
import { PagerDto } from '~/common/dto/pager.dto';
import { Storage } from '~/modules/tools/storage/storage.entity';
import { IsUnique } from '~/shared/database/constraints/unique.constraint';
import { SaleQuotationGroupEntity } from './sale_quotation_group.entity';
export class SaleQuotationGroupDto {
@ApiProperty({ description: '报价分组名称' })
@IsUnique(SaleQuotationGroupEntity, { message: '已存在同名报价分组' })
@IsString()
name: string;
}
export class SaleQuotationGroupUpdateDto extends PartialType(SaleQuotationGroupDto) {}
export class ComapnyCreateDto extends PartialType(SaleQuotationGroupDto) {}
export class SaleQuotationGroupQueryDto extends IntersectionType(
PagerDto<SaleQuotationGroupDto>,
PartialType(SaleQuotationGroupDto)
) {
@ApiProperty({ description: '报价分组名称' })
@IsOptional()
@IsString()
name: string;
}

View File

@ -1,35 +0,0 @@
import { ApiHideProperty, ApiProperty } from '@nestjs/swagger';
import { Column, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany, Relation } from 'typeorm';
import { CommonEntity } from '~/common/entity/common.entity';
import { Storage } from '~/modules/tools/storage/storage.entity';
import { SaleQuotationComponentEntity } from '../component/sale_quotation_component.entity';
/**
*
*/
@Entity({ name: 'sale_quotation_group' })
export class SaleQuotationGroupEntity extends CommonEntity {
@Column({
name: 'name',
type: 'varchar',
unique: true,
length: 255,
comment: '报价分组名称'
})
@ApiProperty({ description: '报价分组名称' })
name: string;
@Column({ name: 'is_delete', type: 'tinyint', default: 0, comment: '是否删除' })
@ApiProperty({ description: '删除状态0未删除1已删除' })
isDelete: number;
items:any[];
@ManyToMany(() => SaleQuotationComponentEntity, component => component.groups)
@JoinTable({
name: 'sale_quotation_group_component',
joinColumn: { name: 'group_id', referencedColumnName: 'id' },
inverseJoinColumn: { name: 'component_id', referencedColumnName: 'id' }
})
components: Relation<SaleQuotationComponentEntity[]>;
}

View File

@ -1,14 +0,0 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { StorageModule } from '~/modules/tools/storage/storage.module';
import { DatabaseModule } from '~/shared/database/database.module';
import { SaleQuotationGroupService } from './sale_quotation_group.service';
import { SaleQuotationGroupController } from './sale_quotation_group.controller';
import { SaleQuotationGroupEntity } from './sale_quotation_group.entity';
@Module({
imports: [TypeOrmModule.forFeature([SaleQuotationGroupEntity]), DatabaseModule],
controllers: [SaleQuotationGroupController],
providers: [SaleQuotationGroupService]
})
export class SaleQuotationGroupModule {}

View File

@ -1,83 +0,0 @@
import { Injectable } from '@nestjs/common';
import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';
import { EntityManager, Repository } from 'typeorm';
import { Pagination } from '~/helper/paginate/pagination';
import { paginate } from '~/helper/paginate';
import { BusinessException } from '~/common/exceptions/biz.exception';
import { ErrorEnum } from '~/constants/error-code.constant';
import { fieldSearch } from '~/shared/database/field-search';
import { SaleQuotationGroupEntity } from './sale_quotation_group.entity';
import { Storage } from '~/modules/tools/storage/storage.entity';
import {
SaleQuotationGroupDto,
SaleQuotationGroupQueryDto,
SaleQuotationGroupUpdateDto
} from './sale_quotation_group.dto';
@Injectable()
export class SaleQuotationGroupService {
constructor(
@InjectEntityManager() private entityManager: EntityManager,
@InjectRepository(SaleQuotationGroupEntity)
private saleQuotationGroupRepository: Repository<SaleQuotationGroupEntity>
) {}
/**
*
*/
async findAll({
page,
pageSize,
...fields
}: SaleQuotationGroupQueryDto): Promise<Pagination<SaleQuotationGroupEntity>> {
const queryBuilder = this.saleQuotationGroupRepository
.createQueryBuilder('saleQuotationGroup')
.where(fieldSearch(fields))
.andWhere('saleQuotationGroup.isDelete = 0');
return paginate<SaleQuotationGroupEntity>(queryBuilder, {
page,
pageSize
});
}
/**
*
*/
async create(dto: SaleQuotationGroupDto): Promise<void> {
await this.saleQuotationGroupRepository.insert(dto);
}
/**
*
*/
async update(id: number, data: Partial<SaleQuotationGroupUpdateDto>): Promise<void> {
await this.entityManager.transaction(async manager => {
await manager.update(SaleQuotationGroupEntity, id, {
...data
});
});
}
/**
*
*/
async delete(id: number): Promise<void> {
// 比较重要,做逻辑删除
await this.saleQuotationGroupRepository.update(id, { isDelete: 1 });
}
/**
*
*/
async info(id: number) {
const info = await this.saleQuotationGroupRepository
.createQueryBuilder('saleQuotationGroup')
.where({
id
})
.andWhere('saleQuotationGroup.isDelete = 0')
.getOne();
return info;
}
}

View File

@ -1,24 +0,0 @@
import { Controller, Get, Query, Res } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator';
import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator';
import { FastifyReply } from 'fastify';
import { SaleQuotationService } from './sale_quotation.service';
import { IdParam } from '~/common/decorators/id-param.decorator';
export const permissions = definePermission('sale_quotation:sale_quotation', {
EXPORT: 'export'
} as const);
@ApiTags('SaleQuotation - 报价模块')
@ApiSecurityAuth()
@Controller('sale_quotation')
export class SaleQuotationController {
constructor(private saleQuotationService: SaleQuotationService) {}
@Get('export/:id')
@ApiOperation({ summary: '导出报价配置明细' })
@Perm(permissions.EXPORT)
async export(@IdParam() id: number, @Res() res: FastifyReply): Promise<void> {
await this.saleQuotationService.export(id, res);
}
}

View File

@ -1,32 +0,0 @@
import { Module } from '@nestjs/common';
import { SaleQuotationGroupModule } from './group/sale_quotation_group.module';
import { SaleQuotationTemplateModule } from './template/sale_quotation_template.module';
import { SaleQuotationComponentModule } from './component/sale_quotation_component.module';
import { RouterModule } from '@nestjs/core';
import { SaleQuotationController } from './sale_quotation.controller';
import { SaleQuotationService } from './sale_quotation.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { SaleQuotationTemplateEntity } from './template/sale_quotation_template.entity';
import { SaleQuotationGroupEntity } from './group/sale_quotation_group.entity';
import { SaleQuotationComponentEntity } from './component/sale_quotation_component.entity';
const modules = [
SaleQuotationComponentModule,
SaleQuotationGroupModule,
SaleQuotationTemplateModule
];
@Module({
imports: [
...modules,
TypeOrmModule.forFeature([SaleQuotationTemplateEntity, SaleQuotationGroupEntity,SaleQuotationComponentEntity]),
RouterModule.register([
{
path: 'sale_quotation',
module: SaleQuotationModule,
children: [...modules]
}
])
],
controllers: [SaleQuotationController],
providers: [SaleQuotationService]
})
export class SaleQuotationModule {}

View File

@ -1,111 +0,0 @@
import { Injectable } from '@nestjs/common';
import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';
import { EntityManager, Repository } from 'typeorm';
import { SaleQuotationTemplateEntity } from './template/sale_quotation_template.entity';
import { FastifyReply } from 'fastify';
import * as ExcelJS from 'exceljs';
import { SaleQuotationGroupEntity } from './group/sale_quotation_group.entity';
@Injectable()
export class SaleQuotationService {
constructor(
@InjectEntityManager() private entityManager: EntityManager,
@InjectRepository(SaleQuotationTemplateEntity)
private saleQuotationTemplateRepository: Repository<SaleQuotationTemplateEntity>
) {}
/**
*
*/
async export(templateId: number, res: FastifyReply): Promise<void> {
const ROW_HEIGHT = 20;
const HEADER_FONT_SIZE = 18;
const workbook = new ExcelJS.Workbook();
let data = await this.saleQuotationTemplateRepository.findOneBy({ id: templateId });
const template: JSON = data.template;
const sheet = workbook.addWorksheet(data.name);
if (template != null) {
sheet.mergeCells('A1:H1');
// 设置标题
sheet.getCell('A1').value = '电液控部分配置明细';
// 设置副标题
sheet.mergeCells('A2:F2');
sheet.getCell('A2').value = '支架电液控系统配置明细中间131架+过渡架4架';
sheet.getCell(`G2`).value = '';
sheet.getCell(`H2`).value = '';
const groups = template['data'] as SaleQuotationGroupEntity[];
const headers = [
'过渡',
'名称',
'规格、型号及说明',
'单位',
'数量',
'备 注',
'单价',
'总价'
];
sheet.addRow(headers);
for (let i = 1; i <= 8; i++) {
sheet.getCell(`${String.fromCharCode(64 + i)}1`).style.font = { bold: true };
sheet.getCell(`${String.fromCharCode(64 + i)}2`).style.font = { bold: true };
sheet.getCell(`${String.fromCharCode(64 + i)}3`).style.font = { bold: true };
}
let rowIndex = 3;
for (let groupIndex = 0; groupIndex < groups.length; groupIndex++) {
rowIndex++;
const group = groups[groupIndex];
sheet.mergeCells(`A${rowIndex}:F${rowIndex}`);
sheet.getCell(`A${rowIndex}`).value = group.name;
sheet.getCell(`A${rowIndex}`).style.font = { bold: true };
sheet.getCell(`G${rowIndex}`).value = '';
sheet.getCell(`H${rowIndex}`).value = '';
for (let componentIndex = 0; componentIndex < group.items.length; componentIndex++) {
const item = group.items[componentIndex];
rowIndex++;
sheet.addRow([
`${componentIndex + 1}`,
item.name ?? '',
item.componentSpecification ?? '',
item.unit ?? '',
item.quantity ?? '',
item.remark ?? '',
item.unitPrice ?? '',
item.amount ?? ''
]);
}
}
sheet.getCell(`I${rowIndex - 1}`).value = '总价';
sheet.getCell(`I${rowIndex - 1}`).style.font = { bold: true };
sheet.getCell(`I${rowIndex}`).value = template['totalPrice'];
}
sheet.eachRow((row, index) => {
if (index >= 0) {
row.alignment = { vertical: 'middle', horizontal: 'center' };
row.height = ROW_HEIGHT;
row.eachCell(cell => {
cell.border = {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' }
};
});
}
});
const columnWidthMap = { A: 8, B: 30, C: 25, F: 20 };
sheet.columns.forEach((column, index: number) => {
column.width = columnWidthMap[String.fromCharCode(65 + index)] ?? 10; // Minimum width of 10
});
//读取buffer进行传输
const buffer = await workbook.xlsx.writeBuffer();
res
.header('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
.header(
'Content-Disposition',
`attachment; filename="${encodeURIComponent('导出_excel' + new Date().getTime() + '.xls')}"`
)
.send(buffer);
}
}

View File

@ -1,63 +0,0 @@
import { Body, Controller, Get, Query, Put, Delete, Post } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { ApiSecurityAuth } from '~/common/decorators/swagger.decorator';
import { ApiResult } from '~/common/decorators/api-result.decorator';
import { IdParam } from '~/common/decorators/id-param.decorator';
import { Perm, definePermission } from '~/modules/auth/decorators/permission.decorator';
import {
SaleQuotationTemplateDto,
SaleQuotationTemplateQueryDto,
SaleQuotationTemplateUpdateDto
} from './sale_quotation_template.dto';
import { SaleQuotationTemplateService } from './sale_quotation_template.service';
export const permissions = definePermission('sale_quotation:sale_quotation_template', {
LIST: 'list',
CREATE: 'create',
READ: 'read',
UPDATE: 'update',
DELETE: 'delete'
} as const);
@ApiTags('SaleQuotationTemplate - 报价模板')
@ApiSecurityAuth()
@Controller('sale_quotation_template')
export class SaleQuotationTemplateController {
constructor(private saleQuotationTemplateService: SaleQuotationTemplateService) {}
@Get()
@ApiOperation({ summary: '分页获取报价模板列表' })
@ApiResult({ type: [SaleQuotationTemplateDto], isPage: true })
@Perm(permissions.LIST)
async list(@Query() dto: SaleQuotationTemplateQueryDto) {
return this.saleQuotationTemplateService.findAll(dto);
}
@Get(':id')
@ApiOperation({ summary: '获取报价模板信息' })
@ApiResult({ type: SaleQuotationTemplateDto })
@Perm(permissions.READ)
async info(@IdParam() id: number) {
return this.saleQuotationTemplateService.info(id);
}
@Post()
@ApiOperation({ summary: '新增报价模板' })
@Perm(permissions.CREATE)
async create(@Body() dto: SaleQuotationTemplateDto): Promise<void> {
await this.saleQuotationTemplateService.create(dto);
}
@Put(':id')
@ApiOperation({ summary: '更新报价模板' })
@Perm(permissions.UPDATE)
async update(@IdParam() id: number, @Body() dto: SaleQuotationTemplateUpdateDto): Promise<void> {
await this.saleQuotationTemplateService.update(id, dto);
}
@Delete(':id')
@ApiOperation({ summary: '删除报价模板' })
@Perm(permissions.DELETE)
async delete(@IdParam() id: number): Promise<void> {
await this.saleQuotationTemplateService.delete(id);
}
}

View File

@ -1,29 +0,0 @@
import { ApiProperty, IntersectionType, PartialType } from '@nestjs/swagger';
import { IsOptional, IsString } from 'class-validator';
import { PagerDto } from '~/common/dto/pager.dto';
import { IsUnique } from '~/shared/database/constraints/unique.constraint';
import { SaleQuotationTemplateEntity } from './sale_quotation_template.entity';
export class SaleQuotationTemplateDto {
@ApiProperty({ description: '报价模板名称' })
@IsString()
name: string;
@ApiProperty({ description: '报价模板' })
@IsOptional()
template: JSON;
}
export class SaleQuotationTemplateUpdateDto extends PartialType(SaleQuotationTemplateDto) {}
export class ComapnyCreateDto extends PartialType(SaleQuotationTemplateDto) {}
export class SaleQuotationTemplateQueryDto extends IntersectionType(
PagerDto<SaleQuotationTemplateDto>,
PartialType(SaleQuotationTemplateDto)
) {
@ApiProperty({ description: '报价模板名称' })
@IsOptional()
@IsString()
name: string;
}

View File

@ -1,32 +0,0 @@
import { ApiProperty } from '@nestjs/swagger';
import { Column, Entity } from 'typeorm';
import { CommonEntity } from '~/common/entity/common.entity';
/**
*
*/
@Entity({ name: 'sale_quotation_template' })
export class SaleQuotationTemplateEntity extends CommonEntity {
@Column({
name: 'name',
type: 'varchar',
unique: true,
length: 255,
comment: '报价模板名称'
})
@ApiProperty({ description: '报价模板名称' })
name: string;
@Column({
name: 'template',
type: 'json',
comment: '报价模板',
nullable: true
})
@ApiProperty({ description: '报价模板(JSON)' })
template: JSON;
@Column({ name: 'is_delete', type: 'tinyint', default: 0, comment: '是否删除' })
@ApiProperty({ description: '删除状态0未删除1已删除' })
isDelete: number;
}

View File

@ -1,14 +0,0 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { StorageModule } from '~/modules/tools/storage/storage.module';
import { DatabaseModule } from '~/shared/database/database.module';
import { SaleQuotationTemplateService } from './sale_quotation_template.service';
import { SaleQuotationTemplateController } from './sale_quotation_template.controller';
import { SaleQuotationTemplateEntity } from './sale_quotation_template.entity';
@Module({
imports: [TypeOrmModule.forFeature([SaleQuotationTemplateEntity]), DatabaseModule],
controllers: [SaleQuotationTemplateController],
providers: [SaleQuotationTemplateService],
})
export class SaleQuotationTemplateModule {}

View File

@ -1,96 +0,0 @@
import { Injectable } from '@nestjs/common';
import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';
import { EntityManager, Not, Repository } from 'typeorm';
import { Pagination } from '~/helper/paginate/pagination';
import { paginate } from '~/helper/paginate';
import { BusinessException } from '~/common/exceptions/biz.exception';
import { ErrorEnum } from '~/constants/error-code.constant';
import { fieldSearch } from '~/shared/database/field-search';
import { SaleQuotationTemplateEntity } from './sale_quotation_template.entity';
import { Storage } from '~/modules/tools/storage/storage.entity';
import {
SaleQuotationTemplateDto,
SaleQuotationTemplateQueryDto,
SaleQuotationTemplateUpdateDto
} from './sale_quotation_template.dto';
@Injectable()
export class SaleQuotationTemplateService {
constructor(
@InjectEntityManager() private entityManager: EntityManager,
@InjectRepository(SaleQuotationTemplateEntity)
private saleQuotationTemplateRepository: Repository<SaleQuotationTemplateEntity>
) {}
/**
*
*/
async findAll({
page,
pageSize,
...fields
}: SaleQuotationTemplateQueryDto): Promise<Pagination<SaleQuotationTemplateEntity>> {
const queryBuilder = this.saleQuotationTemplateRepository
.createQueryBuilder('saleQuotationTemplate')
.where(fieldSearch(fields))
.andWhere('saleQuotationTemplate.isDelete = 0')
.addOrderBy('saleQuotationTemplate.createdAt', 'DESC');
return paginate<SaleQuotationTemplateEntity>(queryBuilder, {
page,
pageSize
});
}
/**
*
*/
async create(dto: SaleQuotationTemplateDto): Promise<void> {
const isDuplicated = await this.saleQuotationTemplateRepository.exist({
where: { name: dto.name }
});
if (isDuplicated) {
throw new BusinessException(ErrorEnum.SALE_QUOTATION_TEMPLATE_NAME_DUPLICATE);
}
await this.saleQuotationTemplateRepository.insert(dto);
}
/**
*
*/
async update(id: number, data: Partial<SaleQuotationTemplateUpdateDto>): Promise<void> {
await this.entityManager.transaction(async manager => {
const isDuplicated = await this.saleQuotationTemplateRepository.exist({
where: { name: data.name, id: Not(id) }
});
if (isDuplicated) {
throw new BusinessException(ErrorEnum.SALE_QUOTATION_TEMPLATE_NAME_DUPLICATE);
}
await manager.update(SaleQuotationTemplateEntity, id, {
...data
});
});
}
/**
*
*/
async delete(id: number): Promise<void> {
// 比较重要,做逻辑删除
await this.saleQuotationTemplateRepository.update(id, { isDelete: 1 });
}
/**
*
*/
async info(id: number) {
const info = await this.saleQuotationTemplateRepository
.createQueryBuilder('saleQuotationTemplate')
.where({
id
})
.andWhere('saleQuotationTemplate.isDelete = 0')
.getOne();
return info;
}
}

View File

@ -23,8 +23,6 @@ import {
import { MenuDto, MenuQueryDto, MenuUpdateDto } from './menu.dto'; import { MenuDto, MenuQueryDto, MenuUpdateDto } from './menu.dto';
import { MenuItemInfo } from './menu.model'; import { MenuItemInfo } from './menu.model';
import { MenuService } from './menu.service'; import { MenuService } from './menu.service';
import { IsMobile } from '~/common/decorators/http.decorator';
import { ResourceDeviceEnum } from '~/constants/enum';
export const permissions = definePermission('system:menu', { export const permissions = definePermission('system:menu', {
LIST: 'list', LIST: 'list',
@ -45,9 +43,7 @@ export class MenuController {
@ApiResult({ type: [MenuItemInfo] }) @ApiResult({ type: [MenuItemInfo] })
@Perm(permissions.LIST) @Perm(permissions.LIST)
async list(@Query() dto: MenuQueryDto) { async list(@Query() dto: MenuQueryDto) {
return this.menuService.list({ return this.menuService.list(dto);
...dto
});
} }
@Get(':id') @Get(':id')

View File

@ -3,7 +3,6 @@ import {
IsBoolean, IsBoolean,
IsIn, IsIn,
IsInt, IsInt,
IsNumber,
IsOptional, IsOptional,
IsString, IsString,
Min, Min,
@ -16,11 +15,6 @@ export class MenuDto {
@IsIn([0, 1, 2]) @IsIn([0, 1, 2])
type: number; type: number;
@ApiProperty({ description: '客户端设备类型' })
@IsOptional()
@IsIn([0, 1])
device: number;
@ApiProperty({ description: '父级菜单' }) @ApiProperty({ description: '父级菜单' })
@IsOptional() @IsOptional()
parentId: number; parentId: number;
@ -91,11 +85,4 @@ export class MenuDto {
export class MenuUpdateDto extends PartialType(MenuDto) {} export class MenuUpdateDto extends PartialType(MenuDto) {}
export class MenuQueryDto extends PartialType(MenuDto) { export class MenuQueryDto extends PartialType(MenuDto) {}
@ApiProperty({ description: 'App端的菜单权限' })
@IsNumber()
@IsOptional()
isApp?: number;
}

View File

@ -6,28 +6,28 @@ import { RoleEntity } from '../role/role.entity';
@Entity({ name: 'sys_menu' }) @Entity({ name: 'sys_menu' })
export class MenuEntity extends CommonEntity { export class MenuEntity extends CommonEntity {
@Column({ name: 'parent_id', nullable: true, comment: '父级ID' }) @Column({ name: 'parent_id', nullable: true })
parentId: number; parentId: number;
@Column() @Column()
name: string; name: string;
@Column({ nullable: true, comment: '前端路径' }) @Column({ nullable: true })
path: string; path: string;
@Column({ nullable: true, comment: '权限' }) @Column({ nullable: true })
permission: string; permission: string;
@Column({ type: 'tinyint', default: 0, comment: '类型0-目录 1-菜单 2-权限' }) @Column({ type: 'tinyint', default: 0 })
type: number; type: number;
@Column({ nullable: true, default: '', comment: '图标' }) @Column({ nullable: true, default: '' })
icon: string; icon: string;
@Column({ name: 'order_no', type: 'int', nullable: true, default: 0 }) @Column({ name: 'order_no', type: 'int', nullable: true, default: 0 })
orderNo: number; orderNo: number;
@Column({ name: 'component', nullable: true, comment: '前端组件文件地址' }) @Column({ name: 'component', nullable: true })
component: string; component: string;
@Column({ name: 'is_ext', type: 'boolean', default: false }) @Column({ name: 'is_ext', type: 'boolean', default: false })
@ -48,9 +48,6 @@ export class MenuEntity extends CommonEntity {
@Column({ type: 'tinyint', default: 1 }) @Column({ type: 'tinyint', default: 1 })
status: number; status: number;
@Column({ type: 'tinyint', default: 1,comment: '用户端类型0-APP 1-PC' })
device: number;
@ManyToMany(() => RoleEntity, role => role.menus, { @ManyToMany(() => RoleEntity, role => role.menus, {
onDelete: 'CASCADE' onDelete: 'CASCADE'
}) })

View File

@ -18,7 +18,6 @@ import { deleteEmptyChildren, generatorMenu, generatorRouters } from '~/utils';
import { RoleService } from '../role/role.service'; import { RoleService } from '../role/role.service';
import { MenuDto, MenuQueryDto, MenuUpdateDto } from './menu.dto'; import { MenuDto, MenuQueryDto, MenuUpdateDto } from './menu.dto';
import { ResourceDeviceEnum } from '~/constants/enum';
@Injectable() @Injectable()
export class MenuService { export class MenuService {
@ -33,14 +32,7 @@ export class MenuService {
/** /**
* *
*/ */
async list({ async list({ name, path, permission, component, status }: MenuQueryDto): Promise<MenuEntity[]> {
name,
path,
permission,
component,
status,
isApp
}: MenuQueryDto): Promise<MenuEntity[]> {
const menus = await this.menuRepository.find({ const menus = await this.menuRepository.find({
where: { where: {
...(name && { name: Like(`%${name}%`) }), ...(name && { name: Like(`%${name}%`) }),
@ -74,29 +66,20 @@ export class MenuService {
/** /**
* *
*/ */
async getMenus(uid: number, deviceType: number): Promise<string[]> { async getMenus(uid: number): Promise<string[]> {
const roleIds = await this.roleService.getRoleIdsByUser(uid); const roleIds = await this.roleService.getRoleIdsByUser(uid);
let menus: MenuEntity[] = []; let menus: MenuEntity[] = [];
if (isEmpty(roleIds)) return generatorRouters([]); if (isEmpty(roleIds)) return generatorRouters([]);
if (this.roleService.hasAdminRole(roleIds)) { if (this.roleService.hasAdminRole(roleIds)) {
menus = await this.menuRepository.find({ menus = await this.menuRepository.find({ order: { orderNo: 'ASC' } });
order: { orderNo: 'ASC' },
where: {
...(isNumber(deviceType) ? { device: deviceType } : null)
}
});
} else { } else {
menus = await this.menuRepository menus = await this.menuRepository
.createQueryBuilder('menu') .createQueryBuilder('menu')
.innerJoinAndSelect('menu.roles', 'role') .innerJoinAndSelect('menu.roles', 'role')
.where({
...(isNumber(deviceType) ? { device: deviceType } : null)
})
.andWhere('role.id IN (:...roleIds)', { roleIds }) .andWhere('role.id IN (:...roleIds)', { roleIds })
.orderBy('menu.order_no', 'ASC') .orderBy('menu.order_no', 'ASC')
.getMany(); .getMany();
} }

View File

@ -35,9 +35,4 @@ export class RoleQueryDto extends IntersectionType(PagerDto<RoleDto>, PartialTyp
@IsInt() @IsInt()
@IsOptional() @IsOptional()
status?: number; status?: number;
@ApiProperty({ description: '用于下拉框选择', required: false })
@IsInt()
@IsOptional()
useForSelect: number;
} }

View File

@ -32,17 +32,14 @@ export class RoleService {
pageSize, pageSize,
name, name,
value, value,
status, status
useForSelect
}: RoleQueryDto): Promise<Pagination<RoleEntity>> { }: RoleQueryDto): Promise<Pagination<RoleEntity>> {
const queryBuilder = this.roleRepository.createQueryBuilder('role').where({ const queryBuilder = this.roleRepository.createQueryBuilder('role').where({
...(name ? { name: Like(`%${name}%`) } : null), ...(name ? { name: Like(`%${name}%`) } : null),
...(value ? { value: Like(`%${value}%`) } : null), ...(value ? { value: Like(`%${value}%`) } : null),
...(isNumber(status) ? { status } : null) ...(isNumber(status) ? { status } : null)
}); });
if(useForSelect){
queryBuilder.andWhere('role.id != :id', { id: ROOT_ROLE_ID })
}
return paginate<RoleEntity>(queryBuilder, { return paginate<RoleEntity>(queryBuilder, {
page, page,
pageSize pageSize

View File

@ -8,7 +8,6 @@ import { MaterialsInOutEntity } from '~/modules/materials_inventory/in_out/mater
import { MaterialsInventoryEntity } from '~/modules/materials_inventory/materials_inventory.entity'; import { MaterialsInventoryEntity } from '~/modules/materials_inventory/materials_inventory.entity';
import { ProductEntity } from '~/modules/product/product.entity'; import { ProductEntity } from '~/modules/product/product.entity';
import { ProjectEntity } from '~/modules/project/project.entity'; import { ProjectEntity } from '~/modules/project/project.entity';
import { SaleQuotationComponentEntity } from '~/modules/sale_quotation/component/sale_quotation_component.entity';
import { VehicleUsageEntity } from '~/modules/vehicle_usage/vehicle_usage.entity'; import { VehicleUsageEntity } from '~/modules/vehicle_usage/vehicle_usage.entity';
@Entity({ name: 'tool_storage' }) @Entity({ name: 'tool_storage' })
@ -78,9 +77,4 @@ export class Storage extends CommonEntity {
@ApiHideProperty() @ApiHideProperty()
@ManyToMany(() => VehicleUsageEntity, vu => vu.files) @ManyToMany(() => VehicleUsageEntity, vu => vu.files)
vehicleUsage: Relation<VehicleUsageEntity[]>; vehicleUsage: Relation<VehicleUsageEntity[]>;
@ApiHideProperty()
@ManyToMany(() => SaleQuotationComponentEntity, component => component.files)
saleQuotationComponents: Relation<SaleQuotationComponentEntity[]>;
} }

View File

@ -15,11 +15,10 @@ import {
ValidateIf ValidateIf
} from 'class-validator'; } from 'class-validator';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { DomainType } from '~/common/decorators/domain.decorator';
import { PagerDto } from '~/common/dto/pager.dto'; import { PagerDto } from '~/common/dto/pager.dto';
export class UserDto extends DomainType { export class UserDto {
@ApiProperty({ description: '头像' }) @ApiProperty({ description: '头像' })
@IsOptional() @IsOptional()
@IsString() @IsString()
@ -84,9 +83,9 @@ export class UserDto extends DomainType {
status: number; status: number;
} }
export class UserUpdateDto extends PartialType(UserDto) { } export class UserUpdateDto extends PartialType(UserDto) {}
export class UserQueryDto extends IntersectionType(PagerDto<UserDto>, PartialType(UserDto), DomainType) { export class UserQueryDto extends IntersectionType(PagerDto<UserDto>, PartialType(UserDto)) {
@ApiProperty({ description: '归属大区', example: 1, required: false }) @ApiProperty({ description: '归属大区', example: 1, required: false })
@IsInt() @IsInt()
@IsOptional() @IsOptional()

View File

@ -22,7 +22,6 @@ import { UserPasswordDto } from './dto/password.dto';
import { UserDto, UserQueryDto, UserUpdateDto } from './dto/user.dto'; import { UserDto, UserQueryDto, UserUpdateDto } from './dto/user.dto';
import { UserEntity } from './user.entity'; import { UserEntity } from './user.entity';
import { UserService } from './user.service'; import { UserService } from './user.service';
import { Domain, DomainType, SkDomain } from '~/common/decorators/domain.decorator';
export const permissions = definePermission('system:user', { export const permissions = definePermission('system:user', {
LIST: 'list', LIST: 'list',
@ -42,14 +41,14 @@ export class UserController {
constructor( constructor(
private userService: UserService, private userService: UserService,
private menuService: MenuService private menuService: MenuService
) { } ) {}
@Get() @Get()
@ApiOperation({ summary: '获取用户列表' }) @ApiOperation({ summary: '获取用户列表' })
@ApiResult({ type: [UserEntity], isPage: true }) @ApiResult({ type: [UserEntity], isPage: true })
@Perm(permissions.LIST) @Perm(permissions.LIST)
async list(@Domain() domain: SkDomain, @Query() dto: UserQueryDto) { async list(@Query() dto: UserQueryDto) {
return this.userService.list(dto, domain); return this.userService.list(dto);
} }
@Get(':id') @Get(':id')
@ -62,8 +61,8 @@ export class UserController {
@Post() @Post()
@ApiOperation({ summary: '新增用户' }) @ApiOperation({ summary: '新增用户' })
@Perm(permissions.CREATE) @Perm(permissions.CREATE)
async create(@Domain() domain: SkDomain, @Body() dto: UserDto): Promise<void> { async create(@Body() dto: UserDto): Promise<void> {
await this.userService.create({ ...dto, domain }); await this.userService.create(dto);
} }
@Put(':id') @Put(':id')

View File

@ -13,7 +13,6 @@ import {
OneToMany, OneToMany,
Relation Relation
} from 'typeorm'; } from 'typeorm';
import { SkDomain } from '~/common/decorators/domain.decorator';
import { CommonEntity } from '~/common/entity/common.entity'; import { CommonEntity } from '~/common/entity/common.entity';
@ -74,9 +73,6 @@ export class UserEntity extends CommonEntity {
@Column({ type: 'tinyint', nullable: true, default: 1 }) @Column({ type: 'tinyint', nullable: true, default: 1 })
status: number; status: number;
@Column({ type: 'int', default: 1, comment: '所属域' })
domain: SkDomain;
@ManyToMany(() => RoleEntity, role => role.users) @ManyToMany(() => RoleEntity, role => role.users)
@JoinTable({ @JoinTable({
name: 'sys_user_roles', name: 'sys_user_roles',

View File

@ -28,7 +28,6 @@ import { PasswordUpdateDto } from './dto/password.dto';
import { UserDto, UserQueryDto, UserUpdateDto } from './dto/user.dto'; import { UserDto, UserQueryDto, UserUpdateDto } from './dto/user.dto';
import { UserEntity } from './user.entity'; import { UserEntity } from './user.entity';
import { AccountInfo } from './user.model'; import { AccountInfo } from './user.model';
import { SkDomain } from '~/common/decorators/domain.decorator';
@Injectable() @Injectable()
export class UserService { export class UserService {
@ -72,7 +71,6 @@ export class UserService {
const user: UserEntity = await this.userRepository const user: UserEntity = await this.userRepository
.createQueryBuilder('user') .createQueryBuilder('user')
.leftJoinAndSelect('user.roles', 'role') .leftJoinAndSelect('user.roles', 'role')
.leftJoinAndSelect('user.dept', 'dept')
.where(`user.id = :uid`, { uid }) .where(`user.id = :uid`, { uid })
.getOne(); .getOne();
@ -244,10 +242,16 @@ export class UserService {
/** /**
* *
*/ */
async list( async list({
{ page, pageSize, username, nickname, deptId, email, status, keyword }: UserQueryDto, page,
domain: SkDomain pageSize,
): Promise<Pagination<UserEntity>> { username,
nickname,
deptId,
email,
status,
keyword
}: UserQueryDto): Promise<Pagination<UserEntity>> {
const queryBuilder = this.userRepository const queryBuilder = this.userRepository
.createQueryBuilder('user') .createQueryBuilder('user')
.leftJoinAndSelect('user.dept', 'dept') .leftJoinAndSelect('user.dept', 'dept')
@ -264,13 +268,7 @@ export class UserService {
if (keyword) { if (keyword) {
//关键字模糊查询product的name,productNumber,productSpecification //关键字模糊查询product的name,productNumber,productSpecification
queryBuilder.andWhere( queryBuilder.andWhere(
`(user.nickname like :keyword or user.namePinyin like :keyword '(user.nickname like :keyword or user.namePinyin like :keyword or dept.name like :keyword)',
or dept.name like :keyword
or user.phone like :keyword
or user.email like :keyword
or user.qq like :keyword
or user.remark like :keyword
)`,
{ {
keyword: `%${keyword}%` keyword: `%${keyword}%`
} }
@ -332,7 +330,7 @@ export class UserService {
/** /**
* *
*/ */
async register({ username, ...data }: RegisterDto, domain: SkDomain): Promise<void> { async register({ username, ...data }: RegisterDto): Promise<void> {
const exists = await this.userRepository.findOneBy({ const exists = await this.userRepository.findOneBy({
username username
}); });
@ -347,8 +345,7 @@ export class UserService {
username, username,
password, password,
status: 1, status: 1,
psalt: salt, psalt: salt
domain
}); });
const user = await manager.save(u); const user = await manager.save(u);

View File

@ -1,12 +1,9 @@
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
import { Between, Like, ObjectLiteral } from 'typeorm'; import { Between, Like, ObjectLiteral, ObjectType } from 'typeorm';
import { SkDomain } from '~/common/decorators/domain.decorator';
export const fieldSearch = <T>(entity: Partial<T>): ObjectLiteral => { export const fieldSearch = <T>(entity: Partial<T>): ObjectLiteral => {
let result = {}; let result = {};
for (let key in entity) { for (let key in entity) {
if (key == 'domain') { if (entity.hasOwnProperty(key)) {
result = { ...result, domain: entity['domain'] || -1 };
} else if (entity.hasOwnProperty(key)) {
switch (typeof entity[key]) { switch (typeof entity[key]) {
case 'number': case 'number':
result = { ...result, ...(isNumber(entity[key]) ? { [key]: entity[key] } : null) }; result = { ...result, ...(isNumber(entity[key]) ? { [key]: entity[key] } : null) };