- InvestController/Service/Mapper: updateTrade 추가 - 수정 결과를 시점별로 시뮬레이션해 보유수량이 음수가 되면 거부(매도 과다 방지) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,7 @@ import java.util.List;
|
|||||||
* DELETE /api/account/invest/holdings/{id} 종목 삭제(매매이력 포함)
|
* DELETE /api/account/invest/holdings/{id} 종목 삭제(매매이력 포함)
|
||||||
* GET /api/account/invest/holdings/{id}/trades 매매이력
|
* GET /api/account/invest/holdings/{id}/trades 매매이력
|
||||||
* POST /api/account/invest/holdings/{id}/trades 매수/매도
|
* POST /api/account/invest/holdings/{id}/trades 매수/매도
|
||||||
|
* PUT /api/account/invest/trades/{id} 매매 수정
|
||||||
* DELETE /api/account/invest/trades/{id} 매매 삭제
|
* DELETE /api/account/invest/trades/{id} 매매 삭제
|
||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@@ -93,6 +94,14 @@ public class InvestController {
|
|||||||
return ResponseEntity.status(HttpStatus.CREATED).body(investService.addTrade(id, req, current.getId()));
|
return ResponseEntity.status(HttpStatus.CREATED).body(investService.addTrade(id, req, current.getId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PutMapping("/trades/{id}")
|
||||||
|
public TradeResponse updateTrade(
|
||||||
|
@PathVariable Long id,
|
||||||
|
@Valid @RequestBody TradeRequest req,
|
||||||
|
@RequestAttribute(AuthInterceptor.CURRENT_MEMBER) SessionUser current) {
|
||||||
|
return investService.updateTrade(id, req, current.getId());
|
||||||
|
}
|
||||||
|
|
||||||
@DeleteMapping("/trades/{id}")
|
@DeleteMapping("/trades/{id}")
|
||||||
public ResponseEntity<Void> deleteTrade(
|
public ResponseEntity<Void> deleteTrade(
|
||||||
@PathVariable Long id,
|
@PathVariable Long id,
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ public interface InvestMapper {
|
|||||||
|
|
||||||
int insertTrade(InvestTrade trade);
|
int insertTrade(InvestTrade trade);
|
||||||
|
|
||||||
|
int updateTrade(InvestTrade trade);
|
||||||
|
|
||||||
int deleteTrade(@Param("id") Long id, @Param("memberId") Long memberId);
|
int deleteTrade(@Param("id") Long id, @Param("memberId") Long memberId);
|
||||||
|
|
||||||
int deleteTradesByHolding(@Param("holdingId") Long holdingId, @Param("memberId") Long memberId);
|
int deleteTradesByHolding(@Param("holdingId") Long holdingId, @Param("memberId") Long memberId);
|
||||||
|
|||||||
@@ -138,6 +138,50 @@ public class InvestService {
|
|||||||
return TradeResponse.from(t);
|
return TradeResponse.from(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public TradeResponse updateTrade(Long id, TradeRequest req, Long memberId) {
|
||||||
|
InvestTrade existing = investMapper.findTradeByIdAndMember(id, memberId);
|
||||||
|
if (existing == null) {
|
||||||
|
throw new ApiException(HttpStatus.NOT_FOUND, "매매 내역을 찾을 수 없습니다.");
|
||||||
|
}
|
||||||
|
Long holdingId = existing.getHoldingId();
|
||||||
|
String type = "SELL".equals(req.getTradeType()) ? "SELL" : "BUY";
|
||||||
|
long fee = req.getFee() != null ? req.getFee() : 0L;
|
||||||
|
|
||||||
|
// 편집을 반영한 시점별 시뮬레이션 — 어느 시점에도 보유수량이 음수가 되면 거부
|
||||||
|
java.util.List<InvestTrade> sim = new java.util.ArrayList<>();
|
||||||
|
for (InvestTrade t : investMapper.findTradesByHolding(holdingId, memberId)) {
|
||||||
|
if (t.getId().equals(id)) {
|
||||||
|
sim.add(InvestTrade.builder().id(id).tradeType(type).tradeDate(req.getTradeDate())
|
||||||
|
.quantity(req.getQuantity()).price(req.getPrice()).fee(fee).build());
|
||||||
|
} else {
|
||||||
|
sim.add(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sim.sort(java.util.Comparator.comparing(InvestTrade::getTradeDate)
|
||||||
|
.thenComparing(InvestTrade::getId));
|
||||||
|
BigDecimal running = BigDecimal.ZERO;
|
||||||
|
for (InvestTrade t : sim) {
|
||||||
|
if ("BUY".equals(t.getTradeType())) {
|
||||||
|
running = running.add(t.getQuantity());
|
||||||
|
} else {
|
||||||
|
running = running.subtract(t.getQuantity());
|
||||||
|
if (running.signum() < 0) {
|
||||||
|
throw new ApiException(HttpStatus.BAD_REQUEST,
|
||||||
|
"수정하면 보유수량이 음수가 됩니다. 매도 수량/순서를 확인하세요.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
existing.setTradeType(type);
|
||||||
|
existing.setTradeDate(req.getTradeDate());
|
||||||
|
existing.setQuantity(req.getQuantity());
|
||||||
|
existing.setPrice(req.getPrice());
|
||||||
|
existing.setFee(fee);
|
||||||
|
investMapper.updateTrade(existing);
|
||||||
|
return TradeResponse.from(existing);
|
||||||
|
}
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
public void deleteTrade(Long id, Long memberId) {
|
public void deleteTrade(Long id, Long memberId) {
|
||||||
if (investMapper.findTradeByIdAndMember(id, memberId) == null) {
|
if (investMapper.findTradeByIdAndMember(id, memberId) == null) {
|
||||||
|
|||||||
@@ -87,6 +87,13 @@
|
|||||||
VALUES (#{memberId}, #{holdingId}, #{tradeType}, #{tradeDate}, #{quantity}, #{price}, #{fee}, NOW())
|
VALUES (#{memberId}, #{holdingId}, #{tradeType}, #{tradeDate}, #{quantity}, #{price}, #{fee}, NOW())
|
||||||
</insert>
|
</insert>
|
||||||
|
|
||||||
|
<update id="updateTrade" parameterType="com.sb.web.account.domain.InvestTrade">
|
||||||
|
UPDATE invest_trade
|
||||||
|
SET trade_type = #{tradeType}, trade_date = #{tradeDate},
|
||||||
|
quantity = #{quantity}, price = #{price}, fee = #{fee}
|
||||||
|
WHERE id = #{id} AND member_id = #{memberId}
|
||||||
|
</update>
|
||||||
|
|
||||||
<delete id="deleteTrade">
|
<delete id="deleteTrade">
|
||||||
DELETE FROM invest_trade WHERE id = #{id} AND member_id = #{memberId}
|
DELETE FROM invest_trade WHERE id = #{id} AND member_id = #{memberId}
|
||||||
</delete>
|
</delete>
|
||||||
|
|||||||
Reference in New Issue
Block a user