[Tank] Added Tank
This commit is contained in:
parent
9eb9f21950
commit
8c5b1ce437
|
|
@ -0,0 +1,135 @@
|
|||
package com.goi.erp.controller;
|
||||
|
||||
import com.goi.erp.common.permission.PermissionChecker;
|
||||
import com.goi.erp.common.permission.PermissionSet;
|
||||
import com.goi.erp.dto.TankRequestDto;
|
||||
import com.goi.erp.dto.TankResponseDto;
|
||||
import com.goi.erp.service.TankService;
|
||||
import com.goi.erp.token.PermissionAuthenticationToken;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/tank")
|
||||
@RequiredArgsConstructor
|
||||
public class TankController {
|
||||
|
||||
private final TankService tankService;
|
||||
|
||||
/* ============================================================
|
||||
CREATE
|
||||
============================================================ */
|
||||
@PostMapping
|
||||
public ResponseEntity<TankResponseDto> createTank(
|
||||
@RequestBody TankRequestDto requestDto
|
||||
) {
|
||||
PermissionAuthenticationToken auth = requireAuth();
|
||||
|
||||
PermissionSet permissionSet = auth.getPermissionSet();
|
||||
if (!PermissionChecker.canCreateOPR(permissionSet)) {
|
||||
throw new AccessDeniedException("You do not have permission to create tank");
|
||||
}
|
||||
|
||||
String actor = auth.getName(); // 네 token 구현에 맞게 바꿔도 됨 (ex: getUsername())
|
||||
TankResponseDto response = tankService.createTank(requestDto, actor);
|
||||
|
||||
return new ResponseEntity<>(response, HttpStatus.CREATED);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
READ ALL (no page)
|
||||
============================================================ */
|
||||
@GetMapping
|
||||
public ResponseEntity<List<TankResponseDto>> getAllTanks() {
|
||||
PermissionAuthenticationToken auth = requireAuth();
|
||||
|
||||
PermissionSet permissionSet = auth.getPermissionSet();
|
||||
if (!PermissionChecker.canReadOPRAll(permissionSet)) {
|
||||
throw new AccessDeniedException("You do not have permission to read tank data");
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(
|
||||
tankService.getAllTanks()
|
||||
);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
READ ONE (UUID)
|
||||
============================================================ */
|
||||
@GetMapping("/uuid/{uuid}")
|
||||
public ResponseEntity<TankResponseDto> getTankByUuid(
|
||||
@PathVariable UUID uuid
|
||||
) {
|
||||
PermissionAuthenticationToken auth = requireAuth();
|
||||
|
||||
PermissionSet permissionSet = auth.getPermissionSet();
|
||||
if (!PermissionChecker.canReadOPR(permissionSet)) {
|
||||
throw new AccessDeniedException("You do not have permission to read tank data");
|
||||
}
|
||||
|
||||
return ResponseEntity.ok(
|
||||
tankService.getTank(uuid)
|
||||
);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
UPDATE (UUID)
|
||||
============================================================ */
|
||||
@PatchMapping("/uuid/{uuid}")
|
||||
public ResponseEntity<TankResponseDto> updateTankByUuid(
|
||||
@PathVariable UUID uuid,
|
||||
@RequestBody TankRequestDto requestDto
|
||||
) {
|
||||
PermissionAuthenticationToken auth = requireAuth();
|
||||
|
||||
PermissionSet permissionSet = auth.getPermissionSet();
|
||||
if (!PermissionChecker.canUpdateOPR(permissionSet)) {
|
||||
throw new AccessDeniedException("You do not have permission to update tank");
|
||||
}
|
||||
|
||||
String actor = auth.getName();
|
||||
return ResponseEntity.ok(
|
||||
tankService.updateTank(uuid, requestDto, actor)
|
||||
);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
DEACTIVATE (UUID)
|
||||
============================================================ */
|
||||
@DeleteMapping("/uuid/{uuid}")
|
||||
public ResponseEntity<Void> deactivateTank(
|
||||
@PathVariable UUID uuid
|
||||
) {
|
||||
PermissionAuthenticationToken auth = requireAuth();
|
||||
|
||||
PermissionSet permissionSet = auth.getPermissionSet();
|
||||
if (!PermissionChecker.canUpdateOPR(permissionSet)) {
|
||||
throw new AccessDeniedException("You do not have permission to deactivate tank");
|
||||
}
|
||||
|
||||
String actor = auth.getName();
|
||||
tankService.deactivateTank(uuid, actor);
|
||||
|
||||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
AUTH HELPER
|
||||
============================================================ */
|
||||
private PermissionAuthenticationToken requireAuth() {
|
||||
PermissionAuthenticationToken auth =
|
||||
(PermissionAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
if (auth == null || auth.getPermissionSet() == null) {
|
||||
throw new AccessDeniedException("Permission information is missing");
|
||||
}
|
||||
return auth;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package com.goi.erp.controller;
|
||||
|
||||
import com.goi.erp.entity.Tank;
|
||||
import com.goi.erp.entity.TankInventoryComposition;
|
||||
import com.goi.erp.repository.TankInventoryCompositionRepository;
|
||||
import com.goi.erp.repository.TankRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/tank/inventory/composition")
|
||||
public class TankInventoryCompositionController {
|
||||
|
||||
private final TankRepository tankRepository;
|
||||
private final TankInventoryCompositionRepository compositionRepository;
|
||||
|
||||
// -------------------------
|
||||
// READ (no pagination)
|
||||
// -------------------------
|
||||
|
||||
/**
|
||||
* 특정 탱크의 OIL composition 조회 (HT/DT)
|
||||
* 예: /api/tank-inventory-compositions/by-tank?tnkUuid=...
|
||||
*/
|
||||
@GetMapping("/by-tank")
|
||||
public ResponseEntity<List<TankInventoryComposition>> getByTank(
|
||||
@RequestParam UUID tnkUuid,
|
||||
Authentication auth
|
||||
) {
|
||||
requireAdminOrManager(auth);
|
||||
|
||||
Tank tank = tankRepository.findByTnkUuid(tnkUuid)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Tank not found: " + tnkUuid));
|
||||
|
||||
List<TankInventoryComposition> rows =
|
||||
compositionRepository.findByTicTankIdAndTicMaterialKindOrderByTicOriginTypeAsc(
|
||||
tank.getTnkId(),
|
||||
"OIL"
|
||||
);
|
||||
|
||||
return ResponseEntity.ok(rows);
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
// UPDATE (manual adjust)
|
||||
// -------------------------
|
||||
|
||||
/**
|
||||
* 수동 보정(관리자/매니저)
|
||||
* - ticQty를 원하는 값으로 세팅
|
||||
* - updated_by 자동 반영
|
||||
*
|
||||
* 예: PATCH /api/tank-inventory-compositions/{ticId}/qty?qty=12.5
|
||||
*/
|
||||
@PatchMapping("/{ticId}/qty")
|
||||
public ResponseEntity<TankInventoryComposition> updateQty(
|
||||
@PathVariable Long ticId,
|
||||
@RequestParam java.math.BigDecimal qty,
|
||||
Authentication auth
|
||||
) {
|
||||
requireAdminOrManager(auth);
|
||||
|
||||
TankInventoryComposition comp = compositionRepository.findById(ticId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Composition not found: " + ticId));
|
||||
|
||||
if (qty == null || qty.compareTo(java.math.BigDecimal.ZERO) < 0) {
|
||||
throw new IllegalArgumentException("qty must be >= 0");
|
||||
}
|
||||
|
||||
comp.setTicQty(qty);
|
||||
comp.setTicUpdatedAt(LocalDateTime.now());
|
||||
comp.setTicUpdatedBy(actor(auth));
|
||||
|
||||
TankInventoryComposition saved = compositionRepository.save(comp);
|
||||
return ResponseEntity.ok(saved);
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
// auth utils
|
||||
// -------------------------
|
||||
|
||||
private void requireAdminOrManager(Authentication auth) {
|
||||
if (auth == null || auth.getAuthorities() == null) {
|
||||
throw new SecurityException("Unauthenticated");
|
||||
}
|
||||
|
||||
boolean ok = auth.getAuthorities().stream().anyMatch(a ->
|
||||
"ROLE_ADMIN".equals(a.getAuthority()) || "ROLE_MANAGER".equals(a.getAuthority())
|
||||
);
|
||||
|
||||
if (!ok) {
|
||||
throw new SecurityException("Forbidden");
|
||||
}
|
||||
}
|
||||
|
||||
private String actor(Authentication auth) {
|
||||
return auth != null ? auth.getName() : "SYSTEM";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
package com.goi.erp.controller;
|
||||
|
||||
import com.goi.erp.entity.TankInventory;
|
||||
import com.goi.erp.service.TankInventoryService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.security.Principal;
|
||||
import java.util.UUID;
|
||||
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
@RequestMapping("/tank/inventory")
|
||||
public class TankInventoryController {
|
||||
|
||||
private final TankInventoryService tankInventoryService;
|
||||
|
||||
/**
|
||||
* 단일 탱크 inventory 조회
|
||||
*/
|
||||
@GetMapping("/{tankUuid}")
|
||||
@PreAuthorize("hasAuthority('TANK_READ')")
|
||||
public TankInventory getInventory(@PathVariable UUID tankUuid) {
|
||||
return tankInventoryService.findByTankUuid(tankUuid)
|
||||
.orElseThrow(() ->
|
||||
new IllegalStateException("Inventory not found. tankUuid=" + tankUuid)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* inventory upsert (절대값)
|
||||
*/
|
||||
@PostMapping("/{tankUuid}")
|
||||
@PreAuthorize("hasAuthority('TANK_WRITE')")
|
||||
public TankInventory upsertInventory(
|
||||
@PathVariable UUID tankUuid,
|
||||
@RequestParam String materialKind,
|
||||
@RequestParam BigDecimal qtyTotal,
|
||||
Principal principal
|
||||
) {
|
||||
return tankInventoryService.upsertInventory(
|
||||
tankUuid,
|
||||
materialKind,
|
||||
qtyTotal,
|
||||
principal.getName() // ✅ updatedBy
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* inventory delta 적용
|
||||
*/
|
||||
@PostMapping("/{tankUuid}/delta")
|
||||
@PreAuthorize("hasAuthority('TANK_WRITE')")
|
||||
public TankInventory applyDelta(
|
||||
@PathVariable UUID tankUuid,
|
||||
@RequestParam String materialKind,
|
||||
@RequestParam BigDecimal deltaQty,
|
||||
Principal principal
|
||||
) {
|
||||
return tankInventoryService.applyDelta(
|
||||
tankUuid,
|
||||
materialKind,
|
||||
deltaQty,
|
||||
principal.getName() // ✅ updatedBy
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.goi.erp.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class TankInventoryCompositionRequestDto {
|
||||
|
||||
private UUID ticUuid;
|
||||
|
||||
private UUID tnkUuid; // tnk_id 대용
|
||||
|
||||
private String ticMaterialKind; // OIL / WATER / SLUDGE
|
||||
private String ticOriginType; // HT / DT (OIL일 때만)
|
||||
|
||||
private BigDecimal ticQty; // 현재 잔량
|
||||
|
||||
private LocalDateTime ticLastInAt; // 마지막 유입 시각
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.goi.erp.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class TankInventoryCompositionResponseDto {
|
||||
|
||||
private UUID ticUuid;
|
||||
|
||||
private UUID tnkUuid;
|
||||
private String tnkCode;
|
||||
private String tnkName;
|
||||
|
||||
private String ticMaterialKind; // OIL / WATER / SLUDGE
|
||||
private String ticOriginType; // HT / DT
|
||||
|
||||
private BigDecimal ticQty;
|
||||
private LocalDateTime ticLastInAt;
|
||||
|
||||
private LocalDateTime ticCreatedAt;
|
||||
private String ticCreatedBy;
|
||||
private LocalDateTime ticUpdatedAt;
|
||||
private String ticUpdatedBy;
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.goi.erp.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class TankInventoryRequestDto {
|
||||
|
||||
private UUID tniUuid;
|
||||
|
||||
private UUID tnkUuid; // tnk_id 대용
|
||||
private String tniMaterialKind; // OIL / WATER / SLUDGE / MIXED
|
||||
|
||||
private BigDecimal tniQtyTotal; // 현재 총량
|
||||
|
||||
private LocalDateTime tniLastUpdatedAt;
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.goi.erp.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class TankInventoryResponseDto {
|
||||
|
||||
/* tank */
|
||||
private UUID tnkUuid;
|
||||
private String tnkCode;
|
||||
private String tnkName;
|
||||
|
||||
private UUID tniUuid;
|
||||
private String tniMaterialKind; // OIL / WATER / SLUDGE / MIXED
|
||||
|
||||
private BigDecimal tniQtyTotal;
|
||||
private BigDecimal tniSpaceLeft;
|
||||
private BigDecimal tniStatusPercent;
|
||||
|
||||
private LocalDateTime tniLastUpdatedAt;
|
||||
|
||||
private LocalDateTime tniCreatedAt;
|
||||
private String tniCreatedBy;
|
||||
private LocalDateTime tniUpdatedAt;
|
||||
private String tniUpdatedBy;
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.goi.erp.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class TankInventorySnapshotRequestDto {
|
||||
|
||||
private UUID tnkUuid;
|
||||
|
||||
private String tisMaterialKind; // OIL / WATER / SLUDGE / MIXED
|
||||
private BigDecimal tisQtyTotal;
|
||||
|
||||
private LocalDateTime tisSnapshotAt;
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.goi.erp.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class TankInventorySnapshotResponseDto {
|
||||
|
||||
private UUID tisUuid;
|
||||
|
||||
private UUID tnkUuid;
|
||||
private String tnkCode;
|
||||
private String tnkName;
|
||||
private String tnkType;
|
||||
|
||||
private String tisMaterialKind;
|
||||
private BigDecimal tisQtyTotal;
|
||||
|
||||
private LocalDateTime tisSnapshotAt;
|
||||
|
||||
private LocalDateTime tisCreatedAt;
|
||||
private String tisCreatedBy;
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.goi.erp.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class TankRequestDto {
|
||||
|
||||
private String tnkCode; // T-01, HT-N, W-01
|
||||
private UUID tnkUuid;
|
||||
private String tnkName; // UCO 1, Heating Tank (New)
|
||||
private String tnkType; // GT / RT / HT / WT / DT / DRUM / SLUDGE
|
||||
private BigDecimal tnkCapacity;
|
||||
private String tnkUnit; // TON (default)
|
||||
private Boolean tnkActive; // true / false
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.goi.erp.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class TankResponseDto {
|
||||
|
||||
private UUID tnkUuid;
|
||||
private String tnkCode;
|
||||
private String tnkName;
|
||||
private String tnkType;
|
||||
|
||||
private BigDecimal tnkCapacity;
|
||||
private String tnkUnit;
|
||||
private Boolean tnkActive;
|
||||
|
||||
private LocalDateTime tnkCreatedAt;
|
||||
private String tnkCreatedBy;
|
||||
private LocalDateTime tnkUpdatedAt;
|
||||
private String tnkUpdatedBy;
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.goi.erp.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class TankTransactionLineRequestDto {
|
||||
|
||||
private UUID fromTnkUuid; // nullable (외부 유입)
|
||||
private UUID toTnkUuid; // nullable (외부 반출)
|
||||
|
||||
private UUID ttlUuid; // nullable (외부 유입)
|
||||
private String ttlTxType; // TRANSFER / DRAIN / LOAD / DUMP / ADJUST
|
||||
|
||||
private String ttlMaterialKind; // OIL / WATER / SLUDGE / MIXED
|
||||
private String ttlOriginType; // HT / DT (OIL일 때)
|
||||
|
||||
private BigDecimal ttlQty;
|
||||
|
||||
private LocalDateTime ttlTxAt; // 발생 시각
|
||||
|
||||
// 시스템이 자동 세팅 (보통 null 로 요청)
|
||||
private String ttlProcessRule;
|
||||
|
||||
private String ttlNote;
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package com.goi.erp.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public class TankTransactionLineResponseDto {
|
||||
|
||||
private UUID ttlUuid;
|
||||
|
||||
private String ttlTxType;
|
||||
|
||||
private UUID fromTnkUuid;
|
||||
private String fromTnkCode;
|
||||
private String fromTnkName;
|
||||
|
||||
private UUID toTnkUuid;
|
||||
private String toTnkCode;
|
||||
private String toTnkName;
|
||||
|
||||
private String ttlMaterialKind;
|
||||
private String ttlOriginType;
|
||||
|
||||
private BigDecimal ttlQty;
|
||||
|
||||
private LocalDateTime ttlTxAt;
|
||||
|
||||
private String ttlProcessRule;
|
||||
private String ttlNote;
|
||||
|
||||
private LocalDateTime ttlCreatedAt;
|
||||
private String ttlCreatedBy;
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
package com.goi.erp.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EntityListeners;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.UniqueConstraint;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(
|
||||
name = "tank",
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(name = "uk_tank_code", columnNames = {"tnk_code"})
|
||||
}
|
||||
)
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
public class Tank {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "tnk_id")
|
||||
private Long tnkId;
|
||||
|
||||
@Column(name = "tnk_uuid", unique = true)
|
||||
private UUID tnkUuid;
|
||||
|
||||
@Column(name = "tnk_code", nullable = false, length = 20, unique = true)
|
||||
private String tnkCode; // T-01, HT-N, W-01
|
||||
|
||||
@Column(name = "tnk_name", nullable = false, length = 100)
|
||||
private String tnkName;
|
||||
|
||||
@Column(name = "tnk_type", nullable = false, length = 10)
|
||||
private String tnkType; // GT / RT / HT / WT / DT
|
||||
|
||||
@Column(name = "tnk_capacity", nullable = false, precision = 12, scale = 3)
|
||||
private BigDecimal tnkCapacity;
|
||||
|
||||
@Column(name = "tnk_unit", length = 10)
|
||||
private String tnkUnit; // TON
|
||||
|
||||
@Column(name = "tnk_active")
|
||||
private Boolean tnkActive;
|
||||
|
||||
@Column(name = "tnk_created_at")
|
||||
private LocalDateTime tnkCreatedAt;
|
||||
|
||||
@Column(name = "tnk_created_by", length = 50)
|
||||
private String tnkCreatedBy;
|
||||
|
||||
@Column(name = "tnk_updated_at")
|
||||
private LocalDateTime tnkUpdatedAt;
|
||||
|
||||
@Column(name = "tnk_updated_by", length = 50)
|
||||
private String tnkUpdatedBy;
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
package com.goi.erp.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EntityListeners;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.UniqueConstraint;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(
|
||||
name = "tank_inventory",
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(name = "uk_tank_inventory_tank", columnNames = {"tni_tank_id"})
|
||||
}
|
||||
)
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
public class TankInventory {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "tni_id")
|
||||
private Long tniId;
|
||||
|
||||
@Column(name = "tni_uuid", unique = true)
|
||||
private UUID tniUuid;
|
||||
|
||||
@Column(name = "tni_tank_id", nullable = false)
|
||||
private Long tniTankId;
|
||||
|
||||
@Column(name = "tni_material_kind", nullable = false, length = 10)
|
||||
private String tniMaterialKind; // OIL / WATER / SLUDGE / MIXED
|
||||
|
||||
@Column(name = "tni_qty_total", nullable = false, precision = 12, scale = 3)
|
||||
private BigDecimal tniQtyTotal;
|
||||
|
||||
@Column(name = "tni_space_left", nullable = false, precision = 12, scale = 3)
|
||||
private BigDecimal tniSpaceLeft;
|
||||
|
||||
@Column(name = "tni_status_percent", nullable = false, precision = 5, scale = 2)
|
||||
private BigDecimal tniStatusPercent;
|
||||
|
||||
@Column(name = "tni_last_updated_at", nullable = false)
|
||||
private LocalDateTime tniLastUpdatedAt;
|
||||
|
||||
@Column(name = "tni_created_at")
|
||||
private LocalDateTime tniCreatedAt;
|
||||
|
||||
@Column(name = "tni_created_by", length = 50)
|
||||
private String tniCreatedBy;
|
||||
|
||||
@Column(name = "tni_updated_at")
|
||||
private LocalDateTime tniUpdatedAt;
|
||||
|
||||
@Column(name = "tni_updated_by", length = 50)
|
||||
private String tniUpdatedBy;
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
package com.goi.erp.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EntityListeners;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.UniqueConstraint;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(
|
||||
name = "tank_inventory_composition",
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(
|
||||
name = "uk_tank_inventory_composition",
|
||||
columnNames = {"tic_tank_id", "tic_material_kind", "tic_origin_type"}
|
||||
)
|
||||
}
|
||||
)
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
public class TankInventoryComposition {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "tic_id")
|
||||
private Long ticId;
|
||||
|
||||
@Column(name = "tic_uuid", unique = true)
|
||||
private UUID ticUuid;
|
||||
|
||||
@Column(name = "tic_tank_id", nullable = false)
|
||||
private Long ticTankId;
|
||||
|
||||
@Column(name = "tic_material_kind", nullable = false, length = 10)
|
||||
private String ticMaterialKind; // OIL / WATER / SLUDGE
|
||||
|
||||
@Column(name = "tic_origin_type", length = 10)
|
||||
private String ticOriginType; // HT / DT (OIL일 때만)
|
||||
|
||||
@Column(name = "tic_qty", nullable = false, precision = 12, scale = 3)
|
||||
private BigDecimal ticQty;
|
||||
|
||||
@Column(name = "tic_last_in_at", nullable = false)
|
||||
private LocalDateTime ticLastInAt;
|
||||
|
||||
@Column(name = "tic_created_at")
|
||||
private LocalDateTime ticCreatedAt;
|
||||
|
||||
@Column(name = "tic_created_by", length = 50)
|
||||
private String ticCreatedBy;
|
||||
|
||||
@Column(name = "tic_updated_at")
|
||||
private LocalDateTime ticUpdatedAt;
|
||||
|
||||
@Column(name = "tic_updated_by", length = 50)
|
||||
private String ticUpdatedBy;
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package com.goi.erp.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EntityListeners;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.UniqueConstraint;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(
|
||||
name = "tank_inventory_snapshot",
|
||||
uniqueConstraints = {
|
||||
@UniqueConstraint(
|
||||
name = "uk_tank_inventory_snapshot",
|
||||
columnNames = {"tis_tank_id", "tis_snapshot_at"}
|
||||
)
|
||||
}
|
||||
)
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
public class TankInventorySnapshot {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "tis_id")
|
||||
private Long tisId;
|
||||
|
||||
@Column(name = "tis_uuid", unique = true)
|
||||
private UUID tisUuid;
|
||||
|
||||
@Column(name = "tis_tank_id", nullable = false)
|
||||
private Long tisTankId;
|
||||
|
||||
@Column(name = "tis_snapshot_at", nullable = false)
|
||||
private LocalDateTime tisSnapshotAt;
|
||||
|
||||
@Column(name = "tis_material_kind", nullable = false, length = 10)
|
||||
private String tisMaterialKind; // OIL / WATER / SLUDGE / MIXED
|
||||
|
||||
@Column(name = "tis_qty_total", nullable = false, precision = 12, scale = 3)
|
||||
private BigDecimal tisQtyTotal;
|
||||
|
||||
@Column(name = "tis_created_at")
|
||||
private LocalDateTime tisCreatedAt;
|
||||
|
||||
@Column(name = "tis_created_by", length = 50)
|
||||
private String tisCreatedBy;
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
package com.goi.erp.entity;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EntityListeners;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity
|
||||
@Table(name = "tank_transaction_line")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
public class TankTransactionLine {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "ttl_id")
|
||||
private Long ttlId;
|
||||
|
||||
@Column(name = "ttl_uuid", unique = true)
|
||||
private UUID ttlUuid;
|
||||
|
||||
@Column(name = "ttl_tx_type", nullable = false, length = 20)
|
||||
private String ttlTxType; // TRANSFER / DRAIN / LOAD / DUMP / ADJUST
|
||||
|
||||
@Column(name = "ttl_from_tank_id")
|
||||
private Long ttlFromTankId;
|
||||
|
||||
@Column(name = "ttl_to_tank_id")
|
||||
private Long ttlToTankId;
|
||||
|
||||
@Column(name = "ttl_material_kind", nullable = false, length = 10)
|
||||
private String ttlMaterialKind; // OIL / WATER / SLUDGE / MIXED
|
||||
|
||||
@Column(name = "ttl_origin_type", length = 10)
|
||||
private String ttlOriginType; // HT / DT (OIL일 때 필수)
|
||||
|
||||
@Column(name = "ttl_qty", nullable = false, precision = 12, scale = 3)
|
||||
private BigDecimal ttlQty;
|
||||
|
||||
@Column(name = "ttl_tx_at", nullable = false)
|
||||
private LocalDateTime ttlTxAt;
|
||||
|
||||
/*
|
||||
* PRIORITY_HT_THEN_DT : GT 출고 시 HT-origin 먼저 차감
|
||||
* MANUAL : 작업자 수동 지정
|
||||
* CLEAN_DT_ONLY : RT -> DT -> GT (클린 오일)
|
||||
* PROPORTIONAL : 비율 배분 (구 방식)
|
||||
*/
|
||||
@Column(name = "ttl_process_rule", length = 50)
|
||||
private String ttlProcessRule;
|
||||
|
||||
@Column(name = "ttl_note")
|
||||
private String ttlNote;
|
||||
|
||||
@Column(name = "ttl_created_at")
|
||||
private LocalDateTime ttlCreatedAt;
|
||||
|
||||
@Column(name = "ttl_created_by", length = 50)
|
||||
private String ttlCreatedBy;
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
package com.goi.erp.repository;
|
||||
|
||||
import com.goi.erp.entity.TankInventoryComposition;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface TankInventoryCompositionRepository extends JpaRepository<TankInventoryComposition, Long> {
|
||||
|
||||
/**
|
||||
* 탱크별 현재 composition 전체 조회
|
||||
* (주로 GT, WT 대시보드용)
|
||||
*/
|
||||
List<TankInventoryComposition> findByTicTankId(Long ticTankId);
|
||||
List<TankInventoryComposition> findByTicTankIdAndTicMaterialKindOrderByTicOriginTypeAsc(Long ticTankId, String ticMaterialKind);
|
||||
|
||||
|
||||
/**
|
||||
* 특정 탱크 + material + origin 단건 조회
|
||||
* (HT/DT oil 잔량 찾기용)
|
||||
*/
|
||||
Optional<TankInventoryComposition> findByTicTankIdAndTicMaterialKindAndTicOriginType(
|
||||
Long ticTankId,
|
||||
String ticMaterialKind,
|
||||
String ticOriginType
|
||||
);
|
||||
|
||||
/**
|
||||
* 특정 탱크의 OIL composition 전체 조회
|
||||
* (GT 출고 시 HT → DT 차감 계산용)
|
||||
*/
|
||||
List<TankInventoryComposition> findByTicTankIdAndTicMaterialKind(
|
||||
Long ticTankId,
|
||||
String ticMaterialKind
|
||||
);
|
||||
|
||||
/**
|
||||
* 수량 증가 (유입)
|
||||
*/
|
||||
@Modifying
|
||||
@Query("""
|
||||
UPDATE TankInventoryComposition t
|
||||
SET t.ticQty = t.ticQty + :qty,
|
||||
t.ticLastInAt = :txAt,
|
||||
t.ticUpdatedAt = CURRENT_TIMESTAMP
|
||||
WHERE t.ticTankId = :tankId
|
||||
AND t.ticMaterialKind = :materialKind
|
||||
AND t.ticOriginType = :originType
|
||||
""")
|
||||
int increaseQty(
|
||||
@Param("tankId") Long tankId,
|
||||
@Param("materialKind") String materialKind,
|
||||
@Param("originType") String originType,
|
||||
@Param("qty") BigDecimal qty,
|
||||
@Param("txAt") LocalDateTime txAt
|
||||
);
|
||||
|
||||
/**
|
||||
* 수량 감소 (출고 / 이동)
|
||||
*/
|
||||
@Modifying
|
||||
@Query("""
|
||||
UPDATE TankInventoryComposition t
|
||||
SET t.ticQty = t.ticQty - :qty,
|
||||
t.ticUpdatedAt = CURRENT_TIMESTAMP
|
||||
WHERE t.ticTankId = :tankId
|
||||
AND t.ticMaterialKind = :materialKind
|
||||
AND t.ticOriginType = :originType
|
||||
AND t.ticQty >= :qty
|
||||
""")
|
||||
int decreaseQty(
|
||||
@Param("tankId") Long tankId,
|
||||
@Param("materialKind") String materialKind,
|
||||
@Param("originType") String originType,
|
||||
@Param("qty") BigDecimal qty
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.goi.erp.repository;
|
||||
|
||||
import com.goi.erp.entity.TankInventory;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface TankInventoryRepository extends JpaRepository<TankInventory, Long> {
|
||||
|
||||
/**
|
||||
* tank_id 기준 현재 inventory 조회
|
||||
* (1 tank = 1 row 보장)
|
||||
*/
|
||||
Optional<TankInventory> findByTniTankId(Long tniTankId);
|
||||
|
||||
/**
|
||||
* tank_id 기준 inventory 존재 여부
|
||||
*/
|
||||
boolean existsByTniTankId(Long tniTankId);
|
||||
|
||||
/**
|
||||
* material kind 기준 전체 조회
|
||||
* 예: OIL 탱크 전체 집계용
|
||||
*/
|
||||
Iterable<TankInventory> findAllByTniMaterialKind(String tniMaterialKind);
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.goi.erp.repository;
|
||||
|
||||
import com.goi.erp.entity.TankInventorySnapshot;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface TankInventorySnapshotRepository extends JpaRepository<TankInventorySnapshot, Long> {
|
||||
|
||||
/**
|
||||
* 특정 탱크의 특정 시점 기준 가장 최근 스냅샷 (as-of query)
|
||||
* 예: "어제 오전 9시 재고"
|
||||
*/
|
||||
Optional<TankInventorySnapshot>
|
||||
findTopByTisTankIdAndTisSnapshotAtLessThanEqualOrderByTisSnapshotAtDesc(
|
||||
Long tisTankId,
|
||||
LocalDateTime snapshotAt
|
||||
);
|
||||
|
||||
/**
|
||||
* 특정 탱크의 최신 스냅샷
|
||||
*/
|
||||
Optional<TankInventorySnapshot>
|
||||
findTopByTisTankIdOrderByTisSnapshotAtDesc(Long tisTankId);
|
||||
|
||||
/**
|
||||
* 특정 기간 동안의 스냅샷 (차트용)
|
||||
*/
|
||||
List<TankInventorySnapshot>
|
||||
findByTisTankIdAndTisSnapshotAtBetweenOrderByTisSnapshotAtAsc(
|
||||
Long tisTankId,
|
||||
LocalDateTime from,
|
||||
LocalDateTime to
|
||||
);
|
||||
|
||||
/**
|
||||
* 특정 시각에 찍힌 전체 탱크 스냅샷 (예: 오전 9시 전체 현황)
|
||||
*/
|
||||
List<TankInventorySnapshot>
|
||||
findByTisSnapshotAt(LocalDateTime snapshotAt);
|
||||
|
||||
/**
|
||||
* 특정 시각 이전의 전체 탱크 최신 스냅샷
|
||||
* (공장장: "그 시점 전체 재고")
|
||||
*/
|
||||
@Query("""
|
||||
select tis
|
||||
from TankInventorySnapshot tis
|
||||
where tis.tisSnapshotAt = (
|
||||
select max(t2.tisSnapshotAt)
|
||||
from TankInventorySnapshot t2
|
||||
where t2.tisTankId = tis.tisTankId
|
||||
and t2.tisSnapshotAt <= :snapshotAt
|
||||
)
|
||||
""")
|
||||
List<TankInventorySnapshot> findLatestSnapshotsAsOf(LocalDateTime snapshotAt);
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.goi.erp.repository;
|
||||
|
||||
import com.goi.erp.entity.Tank;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface TankRepository extends JpaRepository<Tank, Long> {
|
||||
|
||||
Optional<Tank> findByTnkUuid(UUID tnkUuid);
|
||||
|
||||
Optional<Tank> findByTnkCode(String tnkCode);
|
||||
|
||||
boolean existsByTnkUuid(UUID tnkUuid);
|
||||
|
||||
boolean existsByTnkCode(String tnkCode);
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package com.goi.erp.repository;
|
||||
|
||||
import com.goi.erp.entity.TankTransactionLine;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface TankTransactionLineRepository extends JpaRepository<TankTransactionLine, Long>, JpaSpecificationExecutor<TankTransactionLine> {
|
||||
|
||||
/**
|
||||
* UUID 단건 조회 (API 응답용)
|
||||
*/
|
||||
Optional<TankTransactionLine> findByTtlUuid(UUID ttlUuid);
|
||||
|
||||
/**
|
||||
* 특정 탱크 기준 전체 트랜잭션 조회
|
||||
* (from / to 어느 쪽이든 포함)
|
||||
*/
|
||||
List<TankTransactionLine> findByTtlFromTankIdOrTtlToTankId(
|
||||
Long fromTankId,
|
||||
Long toTankId
|
||||
);
|
||||
|
||||
/**
|
||||
* 특정 탱크 + 기간 조회
|
||||
* (감사 / 리포트 / replay 용)
|
||||
*/
|
||||
List<TankTransactionLine> findByTtlFromTankIdAndTtlTxAtBetween(
|
||||
Long tankId,
|
||||
LocalDateTime from,
|
||||
LocalDateTime to
|
||||
);
|
||||
|
||||
List<TankTransactionLine> findByTtlToTankIdAndTtlTxAtBetween(
|
||||
Long tankId,
|
||||
LocalDateTime from,
|
||||
LocalDateTime to
|
||||
);
|
||||
|
||||
/**
|
||||
* 탱크 기준 전체 히스토리 (시간순)
|
||||
*/
|
||||
List<TankTransactionLine> findByTtlFromTankIdOrTtlToTankIdOrderByTtlTxAtAsc(
|
||||
Long fromTankId,
|
||||
Long toTankId
|
||||
);
|
||||
|
||||
/**
|
||||
* 특정 탱크 + material_kind
|
||||
* (예: GT의 OIL만 replay)
|
||||
*/
|
||||
List<TankTransactionLine> findByTtlToTankIdAndTtlMaterialKind(
|
||||
Long tankId,
|
||||
String ttlMaterialKind
|
||||
);
|
||||
|
||||
/**
|
||||
* 특정 탱크에서 빠져나간 트랜잭션
|
||||
* (출고/드레인 분석용)
|
||||
*/
|
||||
List<TankTransactionLine> findByTtlFromTankId(
|
||||
Long tankId
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,135 @@
|
|||
package com.goi.erp.service;
|
||||
|
||||
import com.goi.erp.entity.TankInventoryComposition;
|
||||
import com.goi.erp.entity.TankTransactionLine;
|
||||
import com.goi.erp.repository.TankInventoryCompositionRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class TankInventoryCompositionService {
|
||||
|
||||
private final TankInventoryCompositionRepository compositionRepository;
|
||||
|
||||
/**
|
||||
* 트랜잭션 라인 1건을 composition에 반영
|
||||
* - OIL만 처리
|
||||
* - origin 판단은 tx 생성 시 이미 끝났다는 전제
|
||||
* - from/to 둘 다 있으면: from(-), to(+)
|
||||
*/
|
||||
@Transactional
|
||||
public void applyTransactionLine(TankTransactionLine tx, String actor) {
|
||||
|
||||
if (!"OIL".equalsIgnoreCase(tx.getTtlMaterialKind())) {
|
||||
return;
|
||||
}
|
||||
|
||||
String originType = tx.getTtlOriginType(); // HT / DT
|
||||
if (originType == null || originType.isBlank()) {
|
||||
throw new IllegalStateException("OIL transaction must have originType");
|
||||
}
|
||||
|
||||
BigDecimal qty = tx.getTtlQty();
|
||||
if (qty == null || qty.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new IllegalStateException("ttlQty must be > 0");
|
||||
}
|
||||
|
||||
LocalDateTime txAt = tx.getTtlTxAt() != null ? tx.getTtlTxAt() : LocalDateTime.now();
|
||||
|
||||
// OUT: from tank
|
||||
if (tx.getTtlFromTankId() != null) {
|
||||
applyDelta(tx.getTtlFromTankId(), originType, qty.negate(), txAt, actor);
|
||||
}
|
||||
|
||||
// IN: to tank
|
||||
if (tx.getTtlToTankId() != null) {
|
||||
applyDelta(tx.getTtlToTankId(), originType, qty, txAt, actor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 수동 보정(컨트롤러에서 사용)
|
||||
* - 특정 탱크의 OIL origin(HT/DT) 잔량을 원하는 값으로 세팅
|
||||
*/
|
||||
@Transactional
|
||||
public TankInventoryComposition upsertManual(
|
||||
Long tankId,
|
||||
String originType,
|
||||
BigDecimal newQty,
|
||||
LocalDateTime lastInAt,
|
||||
String actor
|
||||
) {
|
||||
if (tankId == null) throw new IllegalArgumentException("tankId is required");
|
||||
if (originType == null || originType.isBlank()) throw new IllegalArgumentException("originType is required");
|
||||
if (newQty == null || newQty.compareTo(BigDecimal.ZERO) < 0) throw new IllegalArgumentException("newQty must be >= 0");
|
||||
|
||||
String origin = originType.toUpperCase();
|
||||
|
||||
TankInventoryComposition comp = compositionRepository
|
||||
.findByTicTankIdAndTicMaterialKindAndTicOriginType(tankId, "OIL", origin)
|
||||
.orElseGet(() -> TankInventoryComposition.builder()
|
||||
.ticTankId(tankId)
|
||||
.ticMaterialKind("OIL")
|
||||
.ticOriginType(origin)
|
||||
.ticQty(BigDecimal.ZERO)
|
||||
.ticLastInAt(lastInAt != null ? lastInAt : LocalDateTime.now())
|
||||
.ticCreatedAt(LocalDateTime.now())
|
||||
.ticCreatedBy(actor)
|
||||
.build());
|
||||
|
||||
comp.setTicQty(newQty);
|
||||
|
||||
if (lastInAt != null) {
|
||||
comp.setTicLastInAt(lastInAt);
|
||||
}
|
||||
|
||||
comp.setTicUpdatedAt(LocalDateTime.now());
|
||||
comp.setTicUpdatedBy(actor);
|
||||
|
||||
return compositionRepository.save(comp);
|
||||
}
|
||||
|
||||
private void applyDelta(
|
||||
Long tankId,
|
||||
String originType,
|
||||
BigDecimal delta,
|
||||
LocalDateTime txAt,
|
||||
String actor
|
||||
) {
|
||||
String origin = originType.toUpperCase();
|
||||
|
||||
TankInventoryComposition comp = compositionRepository
|
||||
.findByTicTankIdAndTicMaterialKindAndTicOriginType(tankId, "OIL", origin)
|
||||
.orElseGet(() -> TankInventoryComposition.builder()
|
||||
.ticTankId(tankId)
|
||||
.ticMaterialKind("OIL")
|
||||
.ticOriginType(origin)
|
||||
.ticQty(BigDecimal.ZERO)
|
||||
.ticLastInAt(txAt)
|
||||
.ticCreatedAt(LocalDateTime.now())
|
||||
.ticCreatedBy(actor)
|
||||
.build());
|
||||
|
||||
BigDecimal next = comp.getTicQty().add(delta);
|
||||
if (next.compareTo(BigDecimal.ZERO) < 0) {
|
||||
throw new IllegalStateException("composition would go negative. tankId=" + tankId + ", origin=" + origin);
|
||||
}
|
||||
|
||||
comp.setTicQty(next);
|
||||
|
||||
// last_in_at은 유입(+delta)일 때만 갱신
|
||||
if (delta.compareTo(BigDecimal.ZERO) > 0) {
|
||||
comp.setTicLastInAt(txAt);
|
||||
}
|
||||
|
||||
comp.setTicUpdatedAt(LocalDateTime.now());
|
||||
comp.setTicUpdatedBy(actor);
|
||||
|
||||
compositionRepository.save(comp);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
package com.goi.erp.service;
|
||||
|
||||
import com.goi.erp.entity.Tank;
|
||||
import com.goi.erp.entity.TankInventory;
|
||||
import com.goi.erp.repository.TankInventoryRepository;
|
||||
import com.goi.erp.repository.TankRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class TankInventoryService {
|
||||
|
||||
private final TankInventoryRepository tankInventoryRepository;
|
||||
private final TankRepository tankRepository;
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public Optional<TankInventory> findByTankUuid(UUID tankUuid) {
|
||||
Long tankId = getTankIdByUuid(tankUuid);
|
||||
return tankInventoryRepository.findByTniTankId(tankId);
|
||||
}
|
||||
|
||||
/**
|
||||
* inventory 생성 or 갱신 (UPSERT)
|
||||
* 계산 필드는 DB trigger 처리
|
||||
*/
|
||||
@Transactional
|
||||
public TankInventory upsertInventory(
|
||||
UUID tankUuid,
|
||||
String materialKind,
|
||||
BigDecimal qtyTotal,
|
||||
String updatedBy
|
||||
) {
|
||||
if (materialKind == null) {
|
||||
throw new IllegalArgumentException("materialKind is required");
|
||||
}
|
||||
|
||||
Long tankId = getTankIdByUuid(tankUuid);
|
||||
|
||||
TankInventory inventory = tankInventoryRepository
|
||||
.findByTniTankId(tankId)
|
||||
.orElseGet(() -> TankInventory.builder()
|
||||
.tniUuid(UUID.randomUUID()) // ✅ 추가
|
||||
.tniTankId(tankId)
|
||||
.tniCreatedAt(LocalDateTime.now())
|
||||
.tniCreatedBy(updatedBy)
|
||||
.build()
|
||||
);
|
||||
|
||||
inventory.setTniMaterialKind(materialKind);
|
||||
inventory.setTniQtyTotal(qtyTotal);
|
||||
inventory.setTniUpdatedAt(LocalDateTime.now());
|
||||
inventory.setTniUpdatedBy(updatedBy);
|
||||
|
||||
return tankInventoryRepository.save(inventory);
|
||||
}
|
||||
|
||||
/**
|
||||
* 수량 증감 (delta)
|
||||
*/
|
||||
@Transactional
|
||||
public TankInventory applyDelta(
|
||||
UUID tankUuid,
|
||||
String materialKind,
|
||||
BigDecimal deltaQty,
|
||||
String updatedBy
|
||||
) {
|
||||
if (materialKind == null) {
|
||||
throw new IllegalArgumentException("materialKind is required");
|
||||
}
|
||||
|
||||
Long tankId = getTankIdByUuid(tankUuid);
|
||||
|
||||
TankInventory inventory = tankInventoryRepository
|
||||
.findByTniTankId(tankId)
|
||||
.orElseThrow(() ->
|
||||
new IllegalStateException("Tank inventory not found. tankUuid=" + tankUuid)
|
||||
);
|
||||
|
||||
inventory.setTniMaterialKind(materialKind);
|
||||
inventory.setTniQtyTotal(inventory.getTniQtyTotal().add(deltaQty));
|
||||
inventory.setTniUpdatedAt(LocalDateTime.now());
|
||||
inventory.setTniUpdatedBy(updatedBy);
|
||||
|
||||
return tankInventoryRepository.save(inventory);
|
||||
}
|
||||
|
||||
private Long getTankIdByUuid(UUID tankUuid) {
|
||||
Tank tank = tankRepository.findByTnkUuid(tankUuid)
|
||||
.orElseThrow(() ->
|
||||
new IllegalArgumentException("Tank not found. uuid=" + tankUuid)
|
||||
);
|
||||
return tank.getTnkId();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
package com.goi.erp.service;
|
||||
|
||||
import com.goi.erp.dto.TankRequestDto;
|
||||
import com.goi.erp.dto.TankResponseDto;
|
||||
import com.goi.erp.entity.Tank;
|
||||
import com.goi.erp.repository.TankRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class TankService {
|
||||
|
||||
private final TankRepository tankRepository;
|
||||
|
||||
@Transactional
|
||||
public TankResponseDto createTank(TankRequestDto requestDto, String actor) {
|
||||
|
||||
Tank tank = Tank.builder()
|
||||
.tnkUuid(requestDto.getTnkUuid() != null ? requestDto.getTnkUuid() : UUID.randomUUID())
|
||||
.tnkCode(requestDto.getTnkCode())
|
||||
.tnkName(requestDto.getTnkName())
|
||||
.tnkType(requestDto.getTnkType())
|
||||
.tnkCapacity(requestDto.getTnkCapacity())
|
||||
.tnkUnit(requestDto.getTnkUnit() != null ? requestDto.getTnkUnit() : "TON")
|
||||
.tnkActive(requestDto.getTnkActive() != null ? requestDto.getTnkActive() : Boolean.TRUE)
|
||||
.tnkCreatedAt(LocalDateTime.now())
|
||||
.tnkCreatedBy(actor)
|
||||
.tnkUpdatedAt(LocalDateTime.now())
|
||||
.tnkUpdatedBy(actor)
|
||||
.build();
|
||||
|
||||
Tank saved = tankRepository.save(tank);
|
||||
return toResponseDto(saved);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public TankResponseDto getTank(UUID tankUuid) {
|
||||
Tank tank = tankRepository.findByTnkUuid(tankUuid)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Tank not found: " + tankUuid));
|
||||
|
||||
return toResponseDto(tank);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<TankResponseDto> getAllTanks() {
|
||||
return tankRepository.findAll().stream()
|
||||
.map(this::toResponseDto)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public TankResponseDto updateTank(UUID tankUuid, TankRequestDto requestDto, String actor) {
|
||||
|
||||
Tank tank = tankRepository.findByTnkUuid(tankUuid)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Tank not found: " + tankUuid));
|
||||
|
||||
if (requestDto.getTnkCode() != null) {
|
||||
tank.setTnkCode(requestDto.getTnkCode());
|
||||
}
|
||||
if (requestDto.getTnkName() != null) {
|
||||
tank.setTnkName(requestDto.getTnkName());
|
||||
}
|
||||
if (requestDto.getTnkType() != null) {
|
||||
tank.setTnkType(requestDto.getTnkType());
|
||||
}
|
||||
if (requestDto.getTnkCapacity() != null) {
|
||||
tank.setTnkCapacity(requestDto.getTnkCapacity());
|
||||
}
|
||||
if (requestDto.getTnkUnit() != null) {
|
||||
tank.setTnkUnit(requestDto.getTnkUnit());
|
||||
}
|
||||
if (requestDto.getTnkActive() != null) {
|
||||
tank.setTnkActive(requestDto.getTnkActive());
|
||||
}
|
||||
|
||||
tank.setTnkUpdatedAt(LocalDateTime.now());
|
||||
tank.setTnkUpdatedBy(actor);
|
||||
|
||||
Tank saved = tankRepository.save(tank);
|
||||
return toResponseDto(saved);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deactivateTank(UUID tankUuid, String actor) {
|
||||
|
||||
Tank tank = tankRepository.findByTnkUuid(tankUuid)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Tank not found: " + tankUuid));
|
||||
|
||||
tank.setTnkActive(false);
|
||||
tank.setTnkUpdatedAt(LocalDateTime.now());
|
||||
tank.setTnkUpdatedBy(actor);
|
||||
|
||||
tankRepository.save(tank);
|
||||
}
|
||||
|
||||
private TankResponseDto toResponseDto(Tank tank) {
|
||||
return TankResponseDto.builder()
|
||||
.tnkUuid(tank.getTnkUuid())
|
||||
.tnkCode(tank.getTnkCode())
|
||||
.tnkName(tank.getTnkName())
|
||||
.tnkType(tank.getTnkType())
|
||||
.tnkCapacity(tank.getTnkCapacity())
|
||||
.tnkUnit(tank.getTnkUnit())
|
||||
.tnkActive(tank.getTnkActive())
|
||||
.tnkCreatedAt(tank.getTnkCreatedAt())
|
||||
.tnkCreatedBy(tank.getTnkCreatedBy())
|
||||
.tnkUpdatedAt(tank.getTnkUpdatedAt())
|
||||
.tnkUpdatedBy(tank.getTnkUpdatedBy())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,262 @@
|
|||
package com.goi.erp.service;
|
||||
|
||||
import com.goi.erp.dto.TankTransactionLineRequestDto;
|
||||
import com.goi.erp.dto.TankTransactionLineResponseDto;
|
||||
import com.goi.erp.entity.Tank;
|
||||
import com.goi.erp.entity.TankInventory;
|
||||
import com.goi.erp.entity.TankInventoryComposition;
|
||||
import com.goi.erp.entity.TankTransactionLine;
|
||||
import com.goi.erp.repository.TankInventoryCompositionRepository;
|
||||
import com.goi.erp.repository.TankInventoryRepository;
|
||||
import com.goi.erp.repository.TankRepository;
|
||||
import com.goi.erp.repository.TankTransactionLineRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class TankTransactionLineService {
|
||||
|
||||
private final TankRepository tankRepository;
|
||||
private final TankTransactionLineRepository tankTransactionLineRepository;
|
||||
private final TankInventoryRepository tankInventoryRepository;
|
||||
private final TankInventoryCompositionRepository tankInventoryCompositionRepository;
|
||||
|
||||
/**
|
||||
* 트랜잭션 라인 1건 기록 + 현재 재고/컴포지션 반영
|
||||
* - 기본적으로 시스템이 ttlProcessRule 자동 세팅 가능
|
||||
* - from/to 는 UUID로 받고 내부에서 tankId로 resolve
|
||||
*/
|
||||
@Transactional
|
||||
public TankTransactionLineResponseDto createLine(TankTransactionLineRequestDto req, String actor) {
|
||||
validate(req);
|
||||
|
||||
Tank fromTank = resolveTankNullable(req.getFromTnkUuid());
|
||||
Tank toTank = resolveTankNullable(req.getToTnkUuid());
|
||||
|
||||
Long fromTankId = (fromTank == null) ? null : fromTank.getTnkId();
|
||||
Long toTankId = (toTank == null) ? null : toTank.getTnkId();
|
||||
|
||||
// process rule 자동 기본값(원하면 더 엄격히)
|
||||
String processRule = defaultProcessRule(req);
|
||||
|
||||
TankTransactionLine line = TankTransactionLine.builder()
|
||||
.ttlUuid(Optional.ofNullable(req.getTtlUuid()).orElse(UUID.randomUUID())) // dto에 ttlUuid 없으면 제거해도 됨
|
||||
.ttlTxType(req.getTtlTxType())
|
||||
.ttlFromTankId(fromTankId)
|
||||
.ttlToTankId(toTankId)
|
||||
.ttlMaterialKind(req.getTtlMaterialKind())
|
||||
.ttlOriginType(req.getTtlOriginType())
|
||||
.ttlQty(req.getTtlQty())
|
||||
.ttlTxAt(req.getTtlTxAt() != null ? req.getTtlTxAt() : LocalDateTime.now())
|
||||
.ttlProcessRule(processRule)
|
||||
.ttlNote(req.getTtlNote())
|
||||
.ttlCreatedAt(LocalDateTime.now())
|
||||
.ttlCreatedBy(actor)
|
||||
.build();
|
||||
|
||||
TankTransactionLine saved = tankTransactionLineRepository.save(line);
|
||||
|
||||
// 1) inventory 반영 (from -= qty, to += qty)
|
||||
applyInventoryDelta(fromTank, saved.getTtlFromTankId(), saved.getTtlMaterialKind(), saved.getTtlQty().negate(), actor);
|
||||
applyInventoryDelta(toTank, saved.getTtlToTankId(), saved.getTtlMaterialKind(), saved.getTtlQty(), actor);
|
||||
|
||||
// 2) composition 반영 (OIL만, origin HT/DT 유지)
|
||||
// - to 쪽은 +, from 쪽은 -
|
||||
if ("OIL".equalsIgnoreCase(saved.getTtlMaterialKind())) {
|
||||
applyCompositionDelta(saved.getTtlFromTankId(), saved.getTtlOriginType(), saved.getTtlQty().negate(), saved.getTtlTxAt(), actor);
|
||||
applyCompositionDelta(saved.getTtlToTankId(), saved.getTtlOriginType(), saved.getTtlQty(), saved.getTtlTxAt(), actor);
|
||||
}
|
||||
|
||||
return toResponse(saved, fromTank, toTank);
|
||||
}
|
||||
|
||||
// -----------------------
|
||||
// validation / resolve
|
||||
// -----------------------
|
||||
|
||||
private void validate(TankTransactionLineRequestDto req) {
|
||||
if (req.getTtlTxType() == null || req.getTtlTxType().isBlank()) {
|
||||
throw new IllegalArgumentException("ttlTxType is required");
|
||||
}
|
||||
if (req.getTtlMaterialKind() == null || req.getTtlMaterialKind().isBlank()) {
|
||||
throw new IllegalArgumentException("ttlMaterialKind is required");
|
||||
}
|
||||
if (req.getTtlQty() == null || req.getTtlQty().compareTo(BigDecimal.ZERO) <= 0) {
|
||||
throw new IllegalArgumentException("ttlQty must be > 0");
|
||||
}
|
||||
// OIL이면 origin 필수
|
||||
if ("OIL".equalsIgnoreCase(req.getTtlMaterialKind())) {
|
||||
if (req.getTtlOriginType() == null || req.getTtlOriginType().isBlank()) {
|
||||
throw new IllegalArgumentException("ttlOriginType is required when ttlMaterialKind=OIL");
|
||||
}
|
||||
String o = req.getTtlOriginType().toUpperCase();
|
||||
if (!("HT".equals(o) || "DT".equals(o))) {
|
||||
throw new IllegalArgumentException("ttlOriginType must be HT or DT");
|
||||
}
|
||||
}
|
||||
// from/to 둘 다 null이면 의미 없음
|
||||
if (req.getFromTnkUuid() == null && req.getToTnkUuid() == null) {
|
||||
throw new IllegalArgumentException("at least one of fromTankUuid/toTankUuid is required");
|
||||
}
|
||||
}
|
||||
|
||||
private Tank resolveTankNullable(UUID uuid) {
|
||||
if (uuid == null) return null;
|
||||
return tankRepository.findByTnkUuid(uuid)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Tank not found: " + uuid));
|
||||
}
|
||||
|
||||
private String defaultProcessRule(TankTransactionLineRequestDto req) {
|
||||
if (req.getTtlProcessRule() != null && !req.getTtlProcessRule().isBlank()) {
|
||||
return req.getTtlProcessRule();
|
||||
}
|
||||
|
||||
// 자동 기본값: OIL이면 보통 PRIORITY_HT_THEN_DT 또는 CLEAN_DT_ONLY가 될 수 있으나
|
||||
// 여기서는 사용자가 originType을 준 그대로를 존중하고, rule은 최소 표기만 자동.
|
||||
if ("OIL".equalsIgnoreCase(req.getTtlMaterialKind())) {
|
||||
// 출고/드레인/이동 모두 "원칙상 HT 우선 차감"이 작동할 수 있으니 기본값으로 둠
|
||||
return "PRIORITY_HT_THEN_DT";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// -----------------------
|
||||
// inventory / composition updates
|
||||
// -----------------------
|
||||
|
||||
private void applyInventoryDelta(
|
||||
Tank tankOrNull,
|
||||
Long tankId,
|
||||
String materialKind,
|
||||
BigDecimal delta,
|
||||
String actor
|
||||
) {
|
||||
if (tankId == null) return;
|
||||
|
||||
Tank resolvedTank = tankOrNull;
|
||||
if (resolvedTank == null) {
|
||||
// 호출 경로에 따라 tankOrNull이 null일 수 있으니 필요 시 resolve
|
||||
resolvedTank = tankRepository.findById(tankId).orElse(null);
|
||||
}
|
||||
if (resolvedTank == null) return;
|
||||
|
||||
//
|
||||
final Tank tank = resolvedTank;
|
||||
TankInventory inv = tankInventoryRepository.findByTniTankId(tankId)
|
||||
.orElseGet(() -> TankInventory.builder()
|
||||
.tniUuid(UUID.randomUUID())
|
||||
.tniTankId(tankId)
|
||||
.tniMaterialKind(materialKind.toUpperCase())
|
||||
.tniQtyTotal(BigDecimal.ZERO)
|
||||
.tniSpaceLeft(tank.getTnkCapacity()) // 초기: full space
|
||||
.tniStatusPercent(BigDecimal.ZERO)
|
||||
.tniLastUpdatedAt(LocalDateTime.now())
|
||||
.tniCreatedAt(LocalDateTime.now())
|
||||
.tniCreatedBy(actor)
|
||||
.build());
|
||||
|
||||
// material kind 단일 상태 가정: 바뀌는 경우는 "WT가 WATER였다가 OIL로 쓴다" 같은 전환이므로
|
||||
// delta 적용 전에 0인지 체크하고 필요 시 전환 허용(정책은 너가 정하면 됨)
|
||||
inv.setTniMaterialKind(materialKind.toUpperCase());
|
||||
|
||||
BigDecimal newQty = inv.getTniQtyTotal().add(delta);
|
||||
if (newQty.compareTo(BigDecimal.ZERO) < 0) {
|
||||
throw new IllegalStateException("Inventory would go negative. tankId=" + tankId);
|
||||
}
|
||||
inv.setTniQtyTotal(newQty);
|
||||
|
||||
BigDecimal spaceLeft = tank.getTnkCapacity().subtract(newQty);
|
||||
inv.setTniSpaceLeft(spaceLeft);
|
||||
|
||||
BigDecimal percent = BigDecimal.ZERO;
|
||||
if (tank.getTnkCapacity().compareTo(BigDecimal.ZERO) > 0) {
|
||||
percent = newQty.multiply(BigDecimal.valueOf(100))
|
||||
.divide(tank.getTnkCapacity(), 2, java.math.RoundingMode.HALF_UP);
|
||||
}
|
||||
inv.setTniStatusPercent(percent);
|
||||
|
||||
inv.setTniLastUpdatedAt(LocalDateTime.now());
|
||||
inv.setTniUpdatedAt(LocalDateTime.now());
|
||||
inv.setTniUpdatedBy(actor);
|
||||
|
||||
tankInventoryRepository.save(inv);
|
||||
}
|
||||
|
||||
private void applyCompositionDelta(
|
||||
Long tankId,
|
||||
String originType,
|
||||
BigDecimal delta,
|
||||
LocalDateTime txAt,
|
||||
String actor
|
||||
) {
|
||||
if (tankId == null) return;
|
||||
if (originType == null || originType.isBlank()) return;
|
||||
|
||||
String origin = originType.toUpperCase();
|
||||
|
||||
TankInventoryComposition comp = tankInventoryCompositionRepository
|
||||
.findByTicTankIdAndTicMaterialKindAndTicOriginType(tankId, "OIL", origin)
|
||||
.orElseGet(() -> TankInventoryComposition.builder()
|
||||
.ticUuid(UUID.randomUUID())
|
||||
.ticTankId(tankId)
|
||||
.ticMaterialKind("OIL")
|
||||
.ticOriginType(origin)
|
||||
.ticQty(BigDecimal.ZERO)
|
||||
.ticLastInAt(txAt != null ? txAt : LocalDateTime.now())
|
||||
.ticCreatedAt(LocalDateTime.now())
|
||||
.ticCreatedBy(actor)
|
||||
.build());
|
||||
|
||||
BigDecimal newQty = comp.getTicQty().add(delta);
|
||||
if (newQty.compareTo(BigDecimal.ZERO) < 0) {
|
||||
throw new IllegalStateException("Composition would go negative. tankId=" + tankId + ", origin=" + origin);
|
||||
}
|
||||
|
||||
comp.setTicQty(newQty);
|
||||
|
||||
// last_in_at은 +delta(유입)일 때만 갱신
|
||||
if (delta.compareTo(BigDecimal.ZERO) > 0) {
|
||||
comp.setTicLastInAt(txAt != null ? txAt : LocalDateTime.now());
|
||||
}
|
||||
|
||||
comp.setTicUpdatedAt(LocalDateTime.now());
|
||||
comp.setTicUpdatedBy(actor);
|
||||
|
||||
tankInventoryCompositionRepository.save(comp);
|
||||
}
|
||||
|
||||
// -----------------------
|
||||
// mapping
|
||||
// -----------------------
|
||||
|
||||
private TankTransactionLineResponseDto toResponse(TankTransactionLine saved, Tank fromTank, Tank toTank) {
|
||||
return TankTransactionLineResponseDto.builder()
|
||||
.ttlUuid(saved.getTtlUuid())
|
||||
.ttlTxType(saved.getTtlTxType())
|
||||
|
||||
.fromTnkUuid(fromTank == null ? null : fromTank.getTnkUuid())
|
||||
.fromTnkCode(fromTank == null ? null : fromTank.getTnkCode())
|
||||
.fromTnkName(fromTank == null ? null : fromTank.getTnkName())
|
||||
|
||||
.toTnkUuid(toTank == null ? null : toTank.getTnkUuid())
|
||||
.toTnkCode(toTank == null ? null : toTank.getTnkCode())
|
||||
.toTnkName(toTank == null ? null : toTank.getTnkName())
|
||||
|
||||
.ttlMaterialKind(saved.getTtlMaterialKind())
|
||||
.ttlOriginType(saved.getTtlOriginType())
|
||||
.ttlQty(saved.getTtlQty())
|
||||
.ttlTxAt(saved.getTtlTxAt())
|
||||
.ttlProcessRule(saved.getTtlProcessRule())
|
||||
.ttlNote(saved.getTtlNote())
|
||||
.ttlCreatedAt(saved.getTtlCreatedAt())
|
||||
.ttlCreatedBy(saved.getTtlCreatedBy())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue