Compare commits
10 Commits
2df310b1c0
...
b44a108bc0
Author | SHA1 | Date |
---|---|---|
Louis | b44a108bc0 | |
Louis | 081f5e002d | |
louis | 2eebead81c | |
louis | f958ab7af9 | |
louis | cd32cc1ac0 | |
louis | 8ad1a48c60 | |
louis | aaba8dc173 | |
louis | c815e5d561 | |
louis | b4dc00ecd4 | |
louis | 563c08c233 |
|
@ -28,16 +28,18 @@ 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
|
@ -2,7 +2,7 @@
|
||||||
"name": "huaxin-admin",
|
"name": "huaxin-admin",
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@8.10.2",
|
"packageManager": "pnpm@9.1.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18",
|
"node": ">=18",
|
||||||
|
|
14510
pnpm-lock.yaml
14510
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -30,6 +30,9 @@ 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: [
|
||||||
|
@ -73,7 +76,14 @@ import { VehicleUsageModule } from './modules/vehicle_usage/vehicle_usage.module
|
||||||
ProjectModule,
|
ProjectModule,
|
||||||
|
|
||||||
// 车辆管理
|
// 车辆管理
|
||||||
VehicleUsageModule
|
VehicleUsageModule,
|
||||||
|
|
||||||
|
//报价管理
|
||||||
|
SaleQuotationModule,
|
||||||
|
//域
|
||||||
|
DomainModule,
|
||||||
|
// 宝宝管理
|
||||||
|
BabyRecordModule
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{provide: APP_FILTER, useClass: AllExceptionsFilter},
|
{provide: APP_FILTER, useClass: AllExceptionsFilter},
|
||||||
|
@ -88,4 +98,5 @@ import { VehicleUsageModule } from './modules/vehicle_usage/vehicle_usage.module
|
||||||
],
|
],
|
||||||
controllers: []
|
controllers: []
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {
|
||||||
|
}
|
||||||
|
|
|
@ -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 * 30, // limit size 6M
|
fileSize: 1024 * 1024 * 50, // limit size 50M
|
||||||
files: 5 // Max number of file fields
|
files: 5 // Max number of file fields
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
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;
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import type { FastifyRequest } from 'fastify';
|
||||||
|
|
||||||
import { getIp, getIsMobile } from '~/utils/ip.util';
|
import { getIp, getIsMobile } from '~/utils/ip.util';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 快速获取IP
|
* 快速获取IP
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -40,3 +40,14 @@ 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
|
||||||
|
}
|
||||||
|
|
|
@ -61,5 +61,12 @@ 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:域标题已存在'
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ 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)
|
||||||
|
@ -53,7 +54,7 @@ export class AuthController {
|
||||||
|
|
||||||
@Post('register')
|
@Post('register')
|
||||||
@ApiOperation({ summary: '注册' })
|
@ApiOperation({ summary: '注册' })
|
||||||
async register(@Body() dto: RegisterDto): Promise<void> {
|
async register(@Domain() domain: SkDomain, @Body() dto: RegisterDto): Promise<void> {
|
||||||
await this.userService.register(dto);
|
await this.userService.register(dto, domain);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,8 +132,8 @@ export class AuthService {
|
||||||
/**
|
/**
|
||||||
* 获取菜单列表
|
* 获取菜单列表
|
||||||
*/
|
*/
|
||||||
async getMenus(uid: number): Promise<string[]> {
|
async getMenus(uid: number, isApp: number): Promise<string[]> {
|
||||||
return this.menuService.getMenus(uid);
|
return this.menuService.getMenus(uid,isApp);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -14,6 +14,9 @@ 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()
|
||||||
|
@ -45,8 +48,11 @@ export class AccountController {
|
||||||
@ApiOperation({ summary: '获取菜单列表' })
|
@ApiOperation({ summary: '获取菜单列表' })
|
||||||
@ApiResult({ type: [AccountMenus] })
|
@ApiResult({ type: [AccountMenus] })
|
||||||
@AllowAnon()
|
@AllowAnon()
|
||||||
async menu(@AuthUser() user: IAuthUser): Promise<string[]> {
|
async menu(@AuthUser() user: IAuthUser, @IsMobile() isApp: boolean): Promise<string[]> {
|
||||||
return this.authService.getMenus(user.uid);
|
return this.authService.getMenus(
|
||||||
|
user.uid,
|
||||||
|
isApp ? ResourceDeviceEnum.APP : ResourceDeviceEnum.PC
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('permissions')
|
@Get('permissions')
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
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 {
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ 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',
|
||||||
|
@ -34,8 +35,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(@Query() dto: CompanyQueryDto) {
|
async list(@Domain() domain: SkDomain, @Query() dto: CompanyQueryDto) {
|
||||||
return this.companyService.findAll(dto);
|
return this.companyService.findAll({ ...dto, domain });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
|
@ -49,8 +50,8 @@ export class CompanyController {
|
||||||
@Post()
|
@Post()
|
||||||
@ApiOperation({ summary: '新增公司' })
|
@ApiOperation({ summary: '新增公司' })
|
||||||
@Perm(permissions.CREATE)
|
@Perm(permissions.CREATE)
|
||||||
async create(@Body() dto: CompanyDto): Promise<void> {
|
async create(@Domain() domain: SkDomain, @Body() dto: CompanyDto): Promise<void> {
|
||||||
await this.companyService.create(dto);
|
await this.companyService.create({ ...dto, domain });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
|
|
|
@ -15,8 +15,9 @@ 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 {
|
export class CompanyDto extends DomainType {
|
||||||
@ApiProperty({ description: '公司名称' })
|
@ApiProperty({ description: '公司名称' })
|
||||||
@IsUnique(CompanyEntity, { message: '已存在同名公司' })
|
@IsUnique(CompanyEntity, { message: '已存在同名公司' })
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@ -42,7 +43,8 @@ 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()
|
||||||
|
|
|
@ -3,6 +3,7 @@ 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 {
|
||||||
|
@ -20,6 +21,10 @@ 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[]>;
|
||||||
|
|
|
@ -9,6 +9,7 @@ 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 {
|
||||||
|
@ -70,11 +71,7 @@ export class CompanyService {
|
||||||
throw new BusinessException(ErrorEnum.STORAGE_NOT_FOUND);
|
throw new BusinessException(ErrorEnum.STORAGE_NOT_FOUND);
|
||||||
}
|
}
|
||||||
// 附件要批量插入
|
// 附件要批量插入
|
||||||
await manager
|
await manager.createQueryBuilder().relation(CompanyEntity, 'files').of(id).add(fileIds);
|
||||||
.createQueryBuilder()
|
|
||||||
.relation(CompanyEntity, 'files')
|
|
||||||
.of(id)
|
|
||||||
.add(fileIds);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ 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',
|
||||||
|
@ -34,8 +35,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(@Query() dto: ContractQueryDto) {
|
async list(@Domain() domain: SkDomain, @Query() dto: ContractQueryDto) {
|
||||||
return this.contractService.findAll(dto);
|
return this.contractService.findAll({ ...dto, domain });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
|
@ -49,8 +50,8 @@ export class ContractController {
|
||||||
@Post()
|
@Post()
|
||||||
@ApiOperation({ summary: '新增合同' })
|
@ApiOperation({ summary: '新增合同' })
|
||||||
@Perm(permissions.CREATE)
|
@Perm(permissions.CREATE)
|
||||||
async create(@Body() dto: ContractDto): Promise<void> {
|
async create(@Domain() domain: SkDomain, @Body() dto: ContractDto): Promise<void> {
|
||||||
await this.contractService.create(dto);
|
await this.contractService.create({ ...dto, domain });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
|
@ -70,8 +71,10 @@ export class ContractController {
|
||||||
@Put('unlink-attachments/:id')
|
@Put('unlink-attachments/:id')
|
||||||
@ApiOperation({ summary: '附件解除关联' })
|
@ApiOperation({ summary: '附件解除关联' })
|
||||||
@Perm(permissions.UPDATE)
|
@Perm(permissions.UPDATE)
|
||||||
async unlinkAttachments(@IdParam() id: number, @Body() {fileIds}: ContractUpdateDto): Promise<void> {
|
async unlinkAttachments(
|
||||||
|
@IdParam() id: number,
|
||||||
|
@Body() { fileIds }: ContractUpdateDto
|
||||||
|
): Promise<void> {
|
||||||
await this.contractService.unlinkAttachments(id, fileIds);
|
await this.contractService.unlinkAttachments(id, fileIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,8 +15,9 @@ 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 {
|
export class ContractDto extends DomainType {
|
||||||
@ApiProperty({ description: '合同编号' })
|
@ApiProperty({ description: '合同编号' })
|
||||||
@Matches(/^[a-z0-9A-Z]+$/, { message: '合同编号只能包含字母和数字' })
|
@Matches(/^[a-z0-9A-Z]+$/, { message: '合同编号只能包含字母和数字' })
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@ -65,5 +66,7 @@ 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
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ 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 {
|
||||||
|
@ -47,6 +48,10 @@ 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',
|
||||||
|
|
|
@ -10,6 +10,7 @@ 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 {
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
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)) {}
|
|
@ -0,0 +1,15 @@
|
||||||
|
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;
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
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 {}
|
|
@ -0,0 +1,95 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { Body, Controller, Delete, Get, Post, Put, Query } from '@nestjs/common';
|
import { Body, Controller, Delete, Get, Post, Put, Query, Res } 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,15 +9,18 @@ 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 - 原材料出入库记录')
|
||||||
|
@ -25,12 +28,25 @@ export const permissions = definePermission('materials_inventory:history_in_out'
|
||||||
@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(@Query() dto: MaterialsInOutQueryDto) {
|
async list(@Domain() domain: SkDomain, @Query() dto: MaterialsInOutQueryDto) {
|
||||||
return this.materialsInOutService.findAll(dto);
|
return this.materialsInOutService.findAll({ ...dto, domain });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
|
@ -44,8 +60,8 @@ export class MaterialsInOutController {
|
||||||
@Post()
|
@Post()
|
||||||
@ApiOperation({ summary: '新增原材料出入库记录' })
|
@ApiOperation({ summary: '新增原材料出入库记录' })
|
||||||
@Perm(permissions.CREATE)
|
@Perm(permissions.CREATE)
|
||||||
async create(@Body() dto: MaterialsInOutDto): Promise<number> {
|
async create(@Domain() domain: SkDomain, @Body() dto: MaterialsInOutDto): Promise<number> {
|
||||||
return this.materialsInOutService.create(dto);
|
return this.materialsInOutService.create({ ...dto, domain });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
|
|
|
@ -17,12 +17,13 @@ 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 {
|
export class MaterialsInOutDto extends DomainType {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
@ApiProperty({ description: '项目Id' })
|
@ApiProperty({ description: '项目Id' })
|
||||||
|
@ -53,6 +54,9 @@ export class MaterialsInOutDto {
|
||||||
inOrOut: MaterialsInOrOutEnum;
|
inOrOut: MaterialsInOrOutEnum;
|
||||||
|
|
||||||
@ApiProperty({ description: '时间' })
|
@ApiProperty({ description: '时间' })
|
||||||
|
@Transform(params => {
|
||||||
|
return params.value ? new Date(params.value) : null;
|
||||||
|
})
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
time: Date;
|
time: Date;
|
||||||
|
|
||||||
|
@ -97,7 +101,10 @@ export class MaterialsInOutUpdateDto extends PartialType(MaterialsInOutDto) {
|
||||||
@IsArray()
|
@IsArray()
|
||||||
fileIds: number[];
|
fileIds: number[];
|
||||||
}
|
}
|
||||||
export class MaterialsInOutQueryDto extends PagerDto<MaterialsInOutQueryDto> {
|
export class MaterialsInOutQueryDto extends IntersectionType(
|
||||||
|
PagerDto<MaterialsInOutQueryDto>,
|
||||||
|
DomainType
|
||||||
|
) {
|
||||||
@ApiProperty({ description: '出入库时间YYYY-MM-DD' })
|
@ApiProperty({ description: '出入库时间YYYY-MM-DD' })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
// @IsString()
|
// @IsString()
|
||||||
|
@ -152,3 +159,41 @@ export class MaterialsInOutQueryDto extends PagerDto<MaterialsInOutQueryDto> {
|
||||||
@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;
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ 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({
|
||||||
|
@ -129,6 +130,10 @@ 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',
|
||||||
|
|
|
@ -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 { EntityManager, In, Repository } from 'typeorm';
|
import { Between, 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,7 +10,8 @@ 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';
|
||||||
|
@ -19,7 +20,9 @@ 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(
|
||||||
|
@ -32,6 +35,118 @@ export class MaterialsInOutService {
|
||||||
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询所有出入库记录
|
* 查询所有出入库记录
|
||||||
*/
|
*/
|
||||||
|
@ -104,7 +219,8 @@ 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;
|
||||||
|
@ -115,7 +231,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);
|
)({ productId, quantity, unitPrice, projectId, inventoryId, position }, manager, domain);
|
||||||
// 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 }),
|
||||||
|
|
|
@ -13,6 +13,7 @@ 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',
|
||||||
|
@ -33,18 +34,19 @@ 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, res);
|
await this.miService.exportMaterialsInventoryCheck({ ...dto, domain }, 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(@Query() dto: MaterialsInventoryQueryDto) {
|
async list(@Domain() domain: SkDomain, @Query() dto: MaterialsInventoryQueryDto) {
|
||||||
return this.miService.findAll(dto);
|
return this.miService.findAll({ ...dto, domain });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
|
|
|
@ -18,13 +18,15 @@ 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 {}
|
export class MaterialsInventoryDto extends DomainType {}
|
||||||
|
|
||||||
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()
|
||||||
|
@ -46,7 +48,7 @@ export class MaterialsInventoryQueryDto extends IntersectionType(
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
projectId: number;
|
projectId: number;
|
||||||
}
|
}
|
||||||
export class MaterialsInventoryExportDto {
|
export class MaterialsInventoryExportDto extends DomainType {
|
||||||
@ApiProperty({ description: '项目' })
|
@ApiProperty({ description: '项目' })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
|
@ -59,9 +61,14 @@ export class MaterialsInventoryExportDto {
|
||||||
// 开始和结束时间用的是一月的开始和一月的结束的时分秒
|
// 开始和结束时间用的是一月的开始和一月的结束的时分秒
|
||||||
const date = params.value;
|
const date = params.value;
|
||||||
return [
|
return [
|
||||||
date ? `${formatToDate(dayjs(date).startOf('month'))} 00:00:00` : null,
|
date ? `${date[0]} 00:00:00` : null,
|
||||||
date ? `${formatToDate(dayjs(date).endOf('month'))} 23:59:59` : null
|
date ? `${date[1]} 23:59:59` : null
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
time?: string[];
|
time?: string[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '文件名' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
filename: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ 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 {
|
||||||
|
@ -70,6 +71,10 @@ 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;
|
||||||
|
|
|
@ -10,10 +10,11 @@ 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]),
|
TypeOrmModule.forFeature([MaterialsInventoryEntity, MaterialsInOutEntity,ProjectEntity,BabyRecordEntity]),
|
||||||
ParamConfigModule,
|
ParamConfigModule,
|
||||||
StorageModule,
|
StorageModule,
|
||||||
ProjectModule
|
ProjectModule
|
||||||
|
|
|
@ -23,6 +23,7 @@ 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(
|
||||||
|
@ -41,7 +42,7 @@ export class MaterialsInventoryService {
|
||||||
* 导出原材料盘点表
|
* 导出原材料盘点表
|
||||||
*/
|
*/
|
||||||
async exportMaterialsInventoryCheck(
|
async exportMaterialsInventoryCheck(
|
||||||
{ time, projectId }: MaterialsInventoryExportDto,
|
{ time, projectId, domain }: MaterialsInventoryExportDto,
|
||||||
res: FastifyReply
|
res: FastifyReply
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const ROW_HEIGHT = 20;
|
const ROW_HEIGHT = 20;
|
||||||
|
@ -55,7 +56,8 @@ 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']
|
||||||
});
|
});
|
||||||
|
|
||||||
// 生成数据
|
// 生成数据
|
||||||
|
@ -86,7 +88,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.map(item => item.project),
|
data.filter(item => item.inOrOut === MaterialsInOrOutEnum.Out).map(item => item.project),
|
||||||
'id'
|
'id'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -95,7 +97,9 @@ 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(item => item.projectId === project.id);
|
const currentProjectData = data.filter(
|
||||||
|
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]))
|
||||||
|
@ -110,7 +114,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 = [
|
||||||
'出入库单号',
|
'序号',
|
||||||
'公司名称',
|
'公司名称',
|
||||||
'产品名称',
|
'产品名称',
|
||||||
'单位',
|
'单位',
|
||||||
|
@ -128,8 +132,6 @@ export class MaterialsInventoryService {
|
||||||
'结存数量',
|
'结存数量',
|
||||||
'单价',
|
'单价',
|
||||||
'金额',
|
'金额',
|
||||||
'经办人',
|
|
||||||
'领料单号',
|
|
||||||
'备注'
|
'备注'
|
||||||
];
|
];
|
||||||
sheet.addRow(headers);
|
sheet.addRow(headers);
|
||||||
|
@ -152,8 +154,6 @@ 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 <= 21; i++) {
|
for (let i = 16; i <= 19; 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,24 +196,40 @@ export class MaterialsInventoryService {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 根据库存Id分组
|
||||||
const groupedData = groupBy<MaterialsInOutEntity>(
|
const groupedData = groupBy<MaterialsInOutEntity>(
|
||||||
currentMonthProjectData,
|
currentMonthProjectData,
|
||||||
record => `${record.productId}-${record.unitPrice}`
|
record => record.inventoryId
|
||||||
);
|
);
|
||||||
let number = 0;
|
let number = 0;
|
||||||
const groupedInventories = groupBy(
|
const groupedInventories = groupBy(currentProjectInventories, item => item.id);
|
||||||
currentProjectInventories,
|
let orderNo = 0;
|
||||||
item => `${item.projectId}_${item.productId}`
|
|
||||||
);
|
for (const key in groupedInventories) {
|
||||||
for (const key in groupedData) {
|
orderNo++;
|
||||||
// 目前暂定逻辑出库只有一次或者没有出库。不会对一个入库的记录多次出库,故而用find。
|
// 目前暂定逻辑出库只有一次或者没有出库。不会对一个入库的记录多次出库,故而用find。---废弃
|
||||||
const inRecord = groupedData[key].find(item => item.inOrOut === MaterialsInOrOutEnum.In);
|
// 2024.04.16 改成
|
||||||
const outRecord = groupedData[key].find(item => item.inOrOut === MaterialsInOrOutEnum.Out);
|
const inventory = groupedInventories[key][0];
|
||||||
const currInventories =
|
const outRecords = groupedData[key].filter(
|
||||||
groupedInventories[`${inRecord.projectId}_${inRecord.productId}`]?.shift();
|
item => item.inOrOut === MaterialsInOrOutEnum.Out
|
||||||
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;
|
||||||
// 月初库存数量
|
// 月初库存数量
|
||||||
|
@ -234,32 +250,33 @@ export class MaterialsInventoryService {
|
||||||
// 结存库存数量
|
// 结存库存数量
|
||||||
balanceQuantity = calcNumber(
|
balanceQuantity = calcNumber(
|
||||||
currentQuantity,
|
currentQuantity,
|
||||||
calcNumber(inRecord.quantity, outRecord?.quantity || 0, 'subtract'),
|
calcNumber(inRecordQuantity, outRecordQuantity, 'subtract'),
|
||||||
'add'
|
'add'
|
||||||
);
|
);
|
||||||
number++;
|
number++;
|
||||||
sheet.addRow([
|
sheet.addRow([
|
||||||
`${inRecord.inventoryInOutNumber || ''}`,
|
`${orderNo}`,
|
||||||
inRecord.product.company.name || '',
|
inventory.product?.company?.name || '',
|
||||||
inRecord.product.name || '',
|
inventory.product?.name || '',
|
||||||
inRecord.product.unit.label || '',
|
inventory.product.unit.label || '',
|
||||||
currentQuantity,
|
currentQuantity,
|
||||||
parseFloat(`${inRecord.unitPrice || 0}`),
|
parseFloat(`${inventory.unitPrice || 0}`),
|
||||||
calcNumber(currentQuantity, inRecord.unitPrice || 0, 'multiply'),
|
calcNumber(currentQuantity, inventory.unitPrice || 0, 'multiply'),
|
||||||
inRecord.time,
|
// inRecord.time,
|
||||||
inRecord.quantity || 0,
|
'',
|
||||||
parseFloat(`${inRecord.unitPrice || 0}`),
|
inRecordQuantity,
|
||||||
parseFloat(`${inRecord.amount || 0}`),
|
parseFloat(`${inventory.unitPrice || 0}`),
|
||||||
outRecord?.time || '',
|
parseFloat(`${inRecordAmount}`),
|
||||||
outRecord?.quantity || 0,
|
// outRecord?.time || '',
|
||||||
parseFloat(`${outRecord?.unitPrice || 0}`),
|
'',
|
||||||
parseFloat(`${outRecord?.amount || 0}`),
|
outRecordQuantity,
|
||||||
|
parseFloat(`${inventory?.unitPrice || 0}`),
|
||||||
|
parseFloat(`${outRecordAmount}`),
|
||||||
balanceQuantity,
|
balanceQuantity,
|
||||||
parseFloat(`${inRecord?.unitPrice || 0}`),
|
parseFloat(`${inventory?.unitPrice || 0}`),
|
||||||
calcNumber(balanceQuantity, inRecord?.unitPrice || 0, 'multiply'),
|
calcNumber(balanceQuantity, inventory?.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 };
|
||||||
|
@ -319,7 +336,8 @@ 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')
|
||||||
|
@ -339,7 +357,8 @@ export class MaterialsInventoryService {
|
||||||
'product.productSpecification',
|
'product.productSpecification',
|
||||||
'product.productNumber'
|
'product.productNumber'
|
||||||
])
|
])
|
||||||
.where('materialsInventory.isDelete = 0');
|
.where(fieldSearch({ domain }))
|
||||||
|
.andWhere('materialsInventory.isDelete = 0');
|
||||||
if (product) {
|
if (product) {
|
||||||
queryBuilder.andWhere('product.name like :product', { product: `%${product}%` });
|
queryBuilder.andWhere('product.name like :product', { product: `%${product}%` });
|
||||||
}
|
}
|
||||||
|
@ -402,7 +421,8 @@ 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,
|
||||||
|
@ -415,9 +435,9 @@ export class MaterialsInventoryService {
|
||||||
} = data;
|
} = data;
|
||||||
let searchPayload: any = {};
|
let searchPayload: any = {};
|
||||||
if (isDefined(inventoryId)) {
|
if (isDefined(inventoryId)) {
|
||||||
searchPayload = { id: inventoryId };
|
searchPayload = { id: inventoryId, domain };
|
||||||
} else {
|
} else {
|
||||||
searchPayload = { projectId, productId, unitPrice };
|
searchPayload = { projectId, productId, unitPrice, domain };
|
||||||
}
|
}
|
||||||
const exsitedInventory = await manager.findOne(MaterialsInventoryEntity, {
|
const exsitedInventory = await manager.findOne(MaterialsInventoryEntity, {
|
||||||
where: searchPayload, // 根据项目,产品,价格查出之前的实时库存情况
|
where: searchPayload, // 根据项目,产品,价格查出之前的实时库存情况
|
||||||
|
@ -461,7 +481,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;
|
||||||
// 开启悲观行锁,防止脏读和修改
|
// 开启悲观行锁,防止脏读和修改
|
||||||
|
|
|
@ -16,6 +16,7 @@ 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',
|
||||||
|
@ -34,8 +35,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(@Query() dto: ProductQueryDto) {
|
async list(@Domain() domain: SkDomain, @Query() dto: ProductQueryDto) {
|
||||||
return this.productService.findAll(dto);
|
return this.productService.findAll({ ...dto, domain });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
|
@ -49,8 +50,8 @@ export class ProductController {
|
||||||
@Post()
|
@Post()
|
||||||
@ApiOperation({ summary: '新增产品' })
|
@ApiOperation({ summary: '新增产品' })
|
||||||
@Perm(permissions.CREATE)
|
@Perm(permissions.CREATE)
|
||||||
async create(@Body() dto: ProductDto): Promise<void> {
|
async create(@Domain() domain: SkDomain, @Body() dto: ProductDto): Promise<void> {
|
||||||
await this.productService.create(dto);
|
await this.productService.create({ ...dto, domain });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
|
|
|
@ -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 {
|
export class ProductDto extends DomainType {
|
||||||
@ApiProperty({ description: '产品名称' })
|
@ApiProperty({ description: '产品名称' })
|
||||||
@IsString()
|
@IsString()
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
|
||||||
@ApiProperty({ description: '产品规格' })
|
@ApiProperty({ description: '产品规格' })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@ -42,7 +42,8 @@ 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()
|
||||||
|
@ -58,5 +59,4 @@ export class ProductQueryDto extends IntersectionType(
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
keyword?: string;
|
keyword?: string;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ 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({
|
||||||
|
@ -71,6 +72,10 @@ 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',
|
||||||
|
|
|
@ -16,6 +16,7 @@ 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',
|
||||||
|
@ -34,8 +35,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(@Query() dto: ProjectQueryDto) {
|
async list(@Domain() domain: SkDomain, @Query() dto: ProjectQueryDto) {
|
||||||
return this.projectService.findAll(dto);
|
return this.projectService.findAll({ ...dto, domain });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
|
@ -49,8 +50,8 @@ export class ProjectController {
|
||||||
@Post()
|
@Post()
|
||||||
@ApiOperation({ summary: '新增项目' })
|
@ApiOperation({ summary: '新增项目' })
|
||||||
@Perm(permissions.CREATE)
|
@Perm(permissions.CREATE)
|
||||||
async create(@Body() dto: ProjectDto): Promise<void> {
|
async create(@Domain() domain: SkDomain, @Body() dto: ProjectDto): Promise<void> {
|
||||||
await this.projectService.create(dto);
|
await this.projectService.create({ ...dto, domain });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
|
|
|
@ -4,8 +4,9 @@ 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 {
|
export class ProjectDto extends DomainType {
|
||||||
@ApiProperty({ description: '项目名称' })
|
@ApiProperty({ description: '项目名称' })
|
||||||
@IsUnique(ProjectEntity, { message: '已存在同名项目' })
|
@IsUnique(ProjectEntity, { message: '已存在同名项目' })
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@ -31,7 +32,8 @@ 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()
|
||||||
|
|
|
@ -4,6 +4,7 @@ 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';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 项目实体类
|
* 项目实体类
|
||||||
|
@ -24,6 +25,10 @@ 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[]>;
|
||||||
|
|
|
@ -56,11 +56,6 @@ 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')
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
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;
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
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[]>;
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
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 {}
|
|
@ -0,0 +1,161 @@
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
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;
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
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[]>;
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
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 {}
|
|
@ -0,0 +1,83 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
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 {}
|
|
@ -0,0 +1,111 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
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;
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
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;
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
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 {}
|
|
@ -0,0 +1,96 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,8 @@ 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',
|
||||||
|
@ -43,7 +45,9 @@ 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(dto);
|
return this.menuService.list({
|
||||||
|
...dto
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {
|
||||||
IsBoolean,
|
IsBoolean,
|
||||||
IsIn,
|
IsIn,
|
||||||
IsInt,
|
IsInt,
|
||||||
|
IsNumber,
|
||||||
IsOptional,
|
IsOptional,
|
||||||
IsString,
|
IsString,
|
||||||
Min,
|
Min,
|
||||||
|
@ -15,6 +16,11 @@ 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;
|
||||||
|
@ -85,4 +91,11 @@ 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;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -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 })
|
@Column({ name: 'parent_id', nullable: true, comment: '父级ID' })
|
||||||
parentId: number;
|
parentId: number;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true, comment: '前端路径' })
|
||||||
path: string;
|
path: string;
|
||||||
|
|
||||||
@Column({ nullable: true })
|
@Column({ nullable: true, comment: '权限' })
|
||||||
permission: string;
|
permission: string;
|
||||||
|
|
||||||
@Column({ type: 'tinyint', default: 0 })
|
@Column({ type: 'tinyint', default: 0, comment: '类型:0-目录 1-菜单 2-权限' })
|
||||||
type: number;
|
type: number;
|
||||||
|
|
||||||
@Column({ nullable: true, default: '' })
|
@Column({ nullable: true, default: '', comment: '图标' })
|
||||||
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 })
|
@Column({ name: 'component', nullable: true, comment: '前端组件文件地址' })
|
||||||
component: string;
|
component: string;
|
||||||
|
|
||||||
@Column({ name: 'is_ext', type: 'boolean', default: false })
|
@Column({ name: 'is_ext', type: 'boolean', default: false })
|
||||||
|
@ -48,6 +48,9 @@ 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'
|
||||||
})
|
})
|
||||||
|
|
|
@ -18,6 +18,7 @@ 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 {
|
||||||
|
@ -32,7 +33,14 @@ export class MenuService {
|
||||||
/**
|
/**
|
||||||
* 获取所有菜单以及权限
|
* 获取所有菜单以及权限
|
||||||
*/
|
*/
|
||||||
async list({ name, path, permission, component, status }: MenuQueryDto): Promise<MenuEntity[]> {
|
async list({
|
||||||
|
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}%`) }),
|
||||||
|
@ -66,20 +74,29 @@ export class MenuService {
|
||||||
/**
|
/**
|
||||||
* 根据角色获取所有菜单
|
* 根据角色获取所有菜单
|
||||||
*/
|
*/
|
||||||
async getMenus(uid: number): Promise<string[]> {
|
async getMenus(uid: number, deviceType: 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({ order: { orderNo: 'ASC' } });
|
menus = await this.menuRepository.find({
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,4 +35,9 @@ export class RoleQueryDto extends IntersectionType(PagerDto<RoleDto>, PartialTyp
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
status?: number;
|
status?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '用于下拉框选择', required: false })
|
||||||
|
@IsInt()
|
||||||
|
@IsOptional()
|
||||||
|
useForSelect: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,14 +32,17 @@ 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
|
||||||
|
|
|
@ -8,6 +8,7 @@ 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' })
|
||||||
|
@ -77,4 +78,9 @@ 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[]>;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,10 +15,11 @@ 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 {
|
export class UserDto extends DomainType {
|
||||||
@ApiProperty({ description: '头像' })
|
@ApiProperty({ description: '头像' })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
|
@ -85,7 +86,7 @@ export class UserDto {
|
||||||
|
|
||||||
export class UserUpdateDto extends PartialType(UserDto) { }
|
export class UserUpdateDto extends PartialType(UserDto) { }
|
||||||
|
|
||||||
export class UserQueryDto extends IntersectionType(PagerDto<UserDto>, PartialType(UserDto)) {
|
export class UserQueryDto extends IntersectionType(PagerDto<UserDto>, PartialType(UserDto), DomainType) {
|
||||||
@ApiProperty({ description: '归属大区', example: 1, required: false })
|
@ApiProperty({ description: '归属大区', example: 1, required: false })
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|
|
@ -22,6 +22,7 @@ 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',
|
||||||
|
@ -47,8 +48,8 @@ export class UserController {
|
||||||
@ApiOperation({ summary: '获取用户列表' })
|
@ApiOperation({ summary: '获取用户列表' })
|
||||||
@ApiResult({ type: [UserEntity], isPage: true })
|
@ApiResult({ type: [UserEntity], isPage: true })
|
||||||
@Perm(permissions.LIST)
|
@Perm(permissions.LIST)
|
||||||
async list(@Query() dto: UserQueryDto) {
|
async list(@Domain() domain: SkDomain, @Query() dto: UserQueryDto) {
|
||||||
return this.userService.list(dto);
|
return this.userService.list(dto, domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
|
@ -61,8 +62,8 @@ export class UserController {
|
||||||
@Post()
|
@Post()
|
||||||
@ApiOperation({ summary: '新增用户' })
|
@ApiOperation({ summary: '新增用户' })
|
||||||
@Perm(permissions.CREATE)
|
@Perm(permissions.CREATE)
|
||||||
async create(@Body() dto: UserDto): Promise<void> {
|
async create(@Domain() domain: SkDomain, @Body() dto: UserDto): Promise<void> {
|
||||||
await this.userService.create(dto);
|
await this.userService.create({ ...dto, domain });
|
||||||
}
|
}
|
||||||
|
|
||||||
@Put(':id')
|
@Put(':id')
|
||||||
|
|
|
@ -13,6 +13,7 @@ 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';
|
||||||
|
|
||||||
|
@ -73,6 +74,9 @@ 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',
|
||||||
|
|
|
@ -28,6 +28,7 @@ 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 {
|
||||||
|
@ -71,6 +72,7 @@ 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();
|
||||||
|
|
||||||
|
@ -242,16 +244,10 @@ export class UserService {
|
||||||
/**
|
/**
|
||||||
* 查询用户列表
|
* 查询用户列表
|
||||||
*/
|
*/
|
||||||
async list({
|
async list(
|
||||||
page,
|
{ page, pageSize, username, nickname, deptId, email, status, keyword }: UserQueryDto,
|
||||||
pageSize,
|
domain: SkDomain
|
||||||
username,
|
): Promise<Pagination<UserEntity>> {
|
||||||
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')
|
||||||
|
@ -268,7 +264,13 @@ 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 or dept.name like :keyword)',
|
`(user.nickname like :keyword or user.namePinyin 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}%`
|
||||||
}
|
}
|
||||||
|
@ -330,7 +332,7 @@ export class UserService {
|
||||||
/**
|
/**
|
||||||
* 注册
|
* 注册
|
||||||
*/
|
*/
|
||||||
async register({ username, ...data }: RegisterDto): Promise<void> {
|
async register({ username, ...data }: RegisterDto, domain: SkDomain): Promise<void> {
|
||||||
const exists = await this.userRepository.findOneBy({
|
const exists = await this.userRepository.findOneBy({
|
||||||
username
|
username
|
||||||
});
|
});
|
||||||
|
@ -345,7 +347,8 @@ 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);
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import { isNumber } from 'lodash';
|
import { isNumber } from 'lodash';
|
||||||
import { Between, Like, ObjectLiteral, ObjectType } from 'typeorm';
|
import { Between, Like, ObjectLiteral } 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 (entity.hasOwnProperty(key)) {
|
if (key == 'domain') {
|
||||||
|
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) };
|
||||||
|
|
Loading…
Reference in New Issue