This commit is contained in:
2025-12-09 17:02:27 +09:00
parent 26f8e1dab2
commit 83127da569
275 changed files with 139682 additions and 1 deletions

View File

@@ -0,0 +1,81 @@
import { BaseModel } from 'src/common/entities/base.entity';
import { UserModel } from 'src/user/entities/user.entity';
import {
Column,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
/**
* 농장 정보 (tb_farm)
* 1사용자 N농장 관계
*/
@Entity({ name: 'tb_farm' })
export class FarmModel extends BaseModel {
@PrimaryGeneratedColumn({
name: 'pk_farm_no',
type: 'int',
comment: '내부 PK (자동증가)',
})
pkFarmNo: number;
@Column({
name: 'trace_farm_no',
type: 'varchar',
length: 50,
nullable: true,
comment: '축평원 농장번호 (나중에 입력)',
})
traceFarmNo: string;
@Column({
name: 'fk_user_no',
type: 'int',
nullable: true,
comment: '사용자정보 FK',
})
fkUserNo: number;
@Column({
name: 'farmer_name',
type: 'varchar',
length: 100,
nullable: true,
comment: '농장주명 (농가명을 농장주로 사용)',
})
farmerName: string;
@Column({
name: 'region_si',
type: 'varchar',
length: 50,
nullable: true,
comment: '시군',
})
regionSi: string;
@Column({
name: 'region_gu',
type: 'varchar',
length: 50,
nullable: true,
comment: '시/군/구 (지역)',
})
regionGu: string;
@Column({
name: 'road_address',
type: 'varchar',
length: 500,
nullable: true,
comment: '도로명 주소',
})
roadAddress: string;
// Relations
@ManyToOne(() => UserModel, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'fk_user_no' })
user: UserModel;
}

View File

@@ -0,0 +1,52 @@
import { Controller, Get, Post, Put, Delete, Body, Param, Query } from '@nestjs/common';
import { FarmService } from './farm.service';
import { FarmModel } from './entities/farm.entity';
@Controller('farm')
export class FarmController {
constructor(private readonly farmService: FarmService) {}
@Get()
findAll(@Query('userId') userId?: string) {
if (userId) {
return this.farmService.findByUserId(+userId);
}
return this.farmService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.farmService.findOne(+id);
}
/**
* GET /farm/:farmNo/analysis-latest - 농장 최신 분석 의뢰 정보 조회
*/
@Get(':farmNo/analysis-latest')
getLatestAnalysisRequest(@Param('farmNo') farmNo: string) {
return this.farmService.getLatestAnalysisRequest(+farmNo);
}
/**
* GET /farm/:farmNo/analysis-all - 농장 전체 분석 의뢰 목록 조회
*/
@Get(':farmNo/analysis-all')
getAllAnalysisRequests(@Param('farmNo') farmNo: string) {
return this.farmService.getAllAnalysisRequests(+farmNo);
}
@Post()
create(@Body() data: Partial<FarmModel>) {
return this.farmService.create(data);
}
@Put(':id')
update(@Param('id') id: string, @Body() data: Partial<FarmModel>) {
return this.farmService.update(+id, data);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.farmService.remove(+id);
}
}

View File

@@ -0,0 +1,21 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { FarmController } from './farm.controller';
import { FarmService } from './farm.service';
import { FarmModel } from './entities/farm.entity';
import { GenomeRequestModel } from '../genome/entities/genome-request.entity';
import { CowModel } from '../cow/entities/cow.entity';
@Module({
imports: [
TypeOrmModule.forFeature([
FarmModel,
GenomeRequestModel,
CowModel,
]),
],
controllers: [FarmController],
providers: [FarmService],
exports: [FarmService],
})
export class FarmModule {}

View File

@@ -0,0 +1,128 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, IsNull } from 'typeorm';
import { FarmModel } from './entities/farm.entity';
import { GenomeRequestModel } from '../genome/entities/genome-request.entity';
import { CowModel } from '../cow/entities/cow.entity';
import { isValidGenomeAnalysis, VALID_CHIP_SIRE_NAME } from '../common/config/GenomeAnalysisConfig';
@Injectable()
export class FarmService {
constructor(
@InjectRepository(FarmModel)
private readonly farmRepository: Repository<FarmModel>,
@InjectRepository(GenomeRequestModel)
private readonly genomeRequestRepository: Repository<GenomeRequestModel>,
@InjectRepository(CowModel)
private readonly cowRepository: Repository<CowModel>,
) { }
// 전체 농장 조회
async findAll(): Promise<FarmModel[]> {
return this.farmRepository.find({
where: { delDt: IsNull() },
relations: ['user'],
order: { regDt: 'DESC' },
});
}
// 사용자별 농장 조회
async findByUserId(userNo: number): Promise<FarmModel[]> {
return this.farmRepository.find({
where: { fkUserNo: userNo, delDt: IsNull() },
relations: ['user'],
order: { regDt: 'DESC' },
});
}
// 농장 단건 조회
async findOne(id: number): Promise<FarmModel> {
const farm = await this.farmRepository.findOne({
where: { pkFarmNo: id, delDt: IsNull() },
relations: ['user'],
});
if (!farm) {
throw new NotFoundException('Farm #' + id + ' not found');
}
return farm;
}
// 농장 생성
async create(data: Partial<FarmModel>): Promise<FarmModel> {
const farm = this.farmRepository.create(data);
return this.farmRepository.save(farm);
}
// 농장 수정
async update(id: number, data: Partial<FarmModel>): Promise<FarmModel> {
await this.findOne(id);
await this.farmRepository.update(id, data);
return this.findOne(id);
}
// 농장 삭제
async remove(id: number): Promise<void> {
const farm = await this.findOne(id);
await this.farmRepository.softRemove(farm);
}
// 농장 최신 분석 의뢰 정보 조회
async getLatestAnalysisRequest(farmNo: number): Promise<any> {
const farm = await this.findOne(farmNo);
const requests = await this.genomeRequestRepository.find({
where: { fkFarmNo: farmNo, delDt: IsNull() },
relations: ['cow'],
order: { requestDt: 'DESC' },
});
const cows = await this.cowRepository.find({
where: { fkFarmNo: farmNo, delDt: IsNull() },
});
const farmAnlysCnt = requests.length;
const matchCnt = requests.filter(r => isValidGenomeAnalysis(r.chipSireName, r.chipDamName, r.cow?.cowId)).length;
const failCnt = requests.filter(r => r.chipSireName && r.chipSireName !== '일치').length;
const noHistCnt = requests.filter(r => !r.chipSireName).length;
return {
pkFarmAnlysNo: 1,
fkFarmNo: farmNo,
farmAnlysNm: farm.farmerName,
anlysReqDt: requests[0]?.requestDt || new Date(),
region: farm.regionSi,
city: farm.regionGu,
anlysReqCnt: cows.length,
farmAnlysCnt,
matchCnt,
mismatchCnt: failCnt,
failCnt,
noHistCnt,
matchRate: farmAnlysCnt > 0 ? Math.round((matchCnt / farmAnlysCnt) * 100) : 0,
msAnlysCnt: 0,
anlysRmrk: '',
paternities: requests.map(r => ({
pkFarmPaternityNo: r.pkRequestNo,
fkFarmAnlysNo: 1,
receiptDate: r.requestDt,
farmOwnerName: farm.farmerName,
individualNo: r.cow?.cowId || '',
kpnNo: r.cow?.sireKpn || '',
motherIndividualNo: r.cow?.damCowId || '',
hairRootQuality: r.sampleAmount || '',
remarks: r.cowRemarks || '',
fatherMatch: r.chipSireName === '일치' ? '일치' : (r.chipSireName ? '불일치' : '미확인'),
motherMatch: r.chipDamName || '미확인',
reportDate: r.chipReportDt,
})),
};
}
// 농장 전체 분석 의뢰 목록 조회
async getAllAnalysisRequests(farmNo: number): Promise<any[]> {
const latestRequest = await this.getLatestAnalysisRequest(farmNo);
return [latestRequest];
}
}