diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/funds/purchase/PurchaseRecordMapper.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/funds/purchase/PurchaseRecordMapper.java index 3b840086b..020f3b49d 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/funds/purchase/PurchaseRecordMapper.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/funds/purchase/PurchaseRecordMapper.java @@ -111,7 +111,7 @@ public interface PurchaseRecordMapper extends BaseMapperX { * 查询当日销售总额 * @return */ - BigDecimal getTodaySales(); + BigDecimal getTodaySales(@Param("endTime") LocalDateTime endTime); /** * 分页查询销售记录 diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/organamount/OrganAmountServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/organamount/OrganAmountServiceImpl.java index 8eea68484..3175cb25d 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/organamount/OrganAmountServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/organamount/OrganAmountServiceImpl.java @@ -1,5 +1,6 @@ package com.cf.imes.module.system.service.funds.organamount; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.util.ObjectUtil; import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils; @@ -116,7 +117,7 @@ public class OrganAmountServiceImpl implements OrganAmountService { String dateStr = dateList.get(i); List organIncomeExpenseGroupByCreateTimeRespVOS = organIncomeExpenseGroupByCreateTimeRespMap.get(dateStr); - amountArr[i] = organIncomeExpenseGroupByCreateTimeRespVOS != null && !organIncomeExpenseGroupByCreateTimeRespVOS.isEmpty() ? organIncomeExpenseGroupByCreateTimeRespVOS.get(0).getAmount() : BigDecimal.ZERO; + amountArr[i] = CollUtil.isNotEmpty(organIncomeExpenseGroupByCreateTimeRespVOS) ? organIncomeExpenseGroupByCreateTimeRespVOS.get(0).getAmount() : BigDecimal.ZERO; } orgAmountSeriesItem.put("data", amountArr); diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/purchase/PurchaseServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/purchase/PurchaseServiceImpl.java index 5a772532d..2e8423cc3 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/purchase/PurchaseServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/purchase/PurchaseServiceImpl.java @@ -183,6 +183,9 @@ public class PurchaseServiceImpl implements PurchaseService { @Override public void createOrgInitProductPurchaseRecord(Long organId, List productPurchaseReqVOS) { + if (CollUtil.isEmpty(productPurchaseReqVOS)) { + return; + } for (OrganCreateProductPurchaseReqVO purchaseReqVO : productPurchaseReqVOS) { // 1、校验产品 Long productId = purchaseReqVO.getProductId(); @@ -280,14 +283,10 @@ public class PurchaseServiceImpl implements PurchaseService { } // 7、新增 - if (CollUtil.isNotEmpty(createList)) { - createOrgInitProductPurchaseRecord(organId, createList); - } + createOrgInitProductPurchaseRecord(organId, createList); // 8、删除 - if (CollUtil.isNotEmpty(deleteIdList)) { - purchaseRecordMapper.deleteByIds(deleteIdList); - } + purchaseRecordMapper.deleteByIds(deleteIdList); } @Override @@ -551,7 +550,7 @@ public class PurchaseServiceImpl implements PurchaseService { public ManageSalesTotalStatisticRespVO getSalesTotalStatistics() { ManageSalesTotalStatisticRespVO respVO = new ManageSalesTotalStatisticRespVO(); respVO.setSalesTotal(purchaseRecordMapper.getTotalSales().getAmount()); - respVO.setSalesToday(purchaseRecordMapper.getTodaySales()); + respVO.setSalesToday(purchaseRecordMapper.getTodaySales(LocalDateTime.now().plusDays(1))); return respVO; } } diff --git a/cf-module-system/cf-module-system-biz/src/main/resources/mapper/funds/purchase/PurchaseMapper.xml b/cf-module-system/cf-module-system-biz/src/main/resources/mapper/funds/purchase/PurchaseMapper.xml index fc4ffbb08..72e74513d 100644 --- a/cf-module-system/cf-module-system-biz/src/main/resources/mapper/funds/purchase/PurchaseMapper.xml +++ b/cf-module-system/cf-module-system-biz/src/main/resources/mapper/funds/purchase/PurchaseMapper.xml @@ -62,7 +62,7 @@ from purchase_record where deleted = false and initial != true and create_time >= CURDATE() - and create_time < CURDATE() + INTERVAL 1 DAY; + and create_time < #{endTime}; diff --git a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/funds/organamount/OrganAmountServiceImplTest.java b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/funds/organamount/OrganAmountServiceImplTest.java new file mode 100644 index 000000000..a6e5362d6 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/funds/organamount/OrganAmountServiceImplTest.java @@ -0,0 +1,248 @@ +package com.cf.imes.module.system.service.funds.organamount; + +import com.cf.imes.framework.common.enums.StatisticsUnit; +import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils; +import com.cf.imes.framework.security.test.WithMockLoginUser; +import com.cf.imes.framework.test.core.ut.BaseDbAndRedisUnitTest; +import com.cf.imes.module.system.controller.admin.funds.invoice.vo.InvoiceAmountRespVO; +import com.cf.imes.module.system.controller.admin.funds.organamount.vo.ManageAmountOverviewRespVO; +import com.cf.imes.module.system.controller.admin.funds.organamount.vo.OrganFundsStatisticsReqVO; +import com.cf.imes.module.system.controller.admin.funds.organamount.vo.OrganProductOverviewRespVO; +import com.cf.imes.module.system.controller.admin.funds.organamount.vo.TotalSalesAmountRespVO; +import com.cf.imes.module.system.controller.admin.funds.purchase.vo.OrgEarliestPurchaseProductRespVO; +import com.cf.imes.module.system.dal.dataobject.funds.purchase.PurchaseRecordDO; +import com.cf.imes.module.system.dal.dataobject.incomeandexpense.IncomeExpenseDetailsDO; +import com.cf.imes.module.system.dal.mysql.incomeandexpense.IncomeExpenseDetailsMapper; +import com.cf.imes.module.system.service.funds.invoice.InvoiceService; +import com.cf.imes.module.system.service.funds.purchase.PurchaseService; +import com.cf.imes.module.system.service.organ.OrganService; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +import static com.cf.imes.framework.test.core.util.RandomUtils.randomPojo; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * + * + * @author Gqr + * @since 2025/12/31 14:00 + */ +@Import(OrganAmountServiceImpl.class) +public class OrganAmountServiceImplTest extends BaseDbAndRedisUnitTest { + @Resource + IncomeExpenseDetailsMapper incomeExpenseDetailsMapper; + + @Resource + private OrganAmountServiceImpl organAmountService; + + @MockitoBean + private OrganService organService; + + @MockitoBean + private PurchaseService purchaseService; + + @MockitoBean + private InvoiceService invoiceService; + + @Test + @WithMockLoginUser(organId = 1L) + void testGetOrganProductOverview_notInitialPurchase() { + // ===== given ===== + Long organId = SecurityFrameworkUtils.getUserOrganId(); + + PurchaseRecordDO record = new PurchaseRecordDO(); + record.setEndTime(LocalDate.of(2025, 12, 31)); + record.setInitial(false); + record.setPurchaseDuration(12); + record.setPurchaseDurationUnit(1); + + OrgEarliestPurchaseProductRespVO resp = new OrgEarliestPurchaseProductRespVO(record); + + when(purchaseService.getOrgEarliestProductDetail(organId)) + .thenReturn(resp); + when(purchaseService.getInvoicingAmount(organId)) + .thenReturn(new BigDecimal("100.50")); + + // ===== when ===== + OrganProductOverviewRespVO result = organAmountService.getOrganProductOverview(); + + // ===== then ===== + assertNotNull(result); + + assertEquals("2025-12-31 00:00:00", result.getProductEfficientDate()); + assertEquals(12, result.getDuration()); + assertEquals(1, result.getDurationUnit()); + assertEquals(new BigDecimal("100.50"), result.getInvoiceableAmount()); + + verify(organService).validOrgan(organId); + verify(purchaseService).getOrgEarliestProductDetail(organId); + verify(purchaseService).getInvoicingAmount(organId); + } + + @Test + @WithMockLoginUser(organId = 1L) + void testGetOrganProductOverview_noOrgEarliestPurchaseProduct() { + // ===== given ===== + Long organId = SecurityFrameworkUtils.getUserOrganId(); + + when(purchaseService.getOrgEarliestProductDetail(organId)) + .thenReturn(null); + when(purchaseService.getInvoicingAmount(organId)) + .thenReturn(new BigDecimal("100.50")); + + // ===== when ===== + OrganProductOverviewRespVO result = organAmountService.getOrganProductOverview(); + + // ===== then ===== + assertNotNull(result); + + assertNull(result.getProductEfficientDate()); + assertNull(result.getDuration()); + assertNull(result.getDurationUnit()); + assertEquals(new BigDecimal("100.50"), result.getInvoiceableAmount()); + + verify(organService).validOrgan(organId); + verify(purchaseService).getOrgEarliestProductDetail(organId); + verify(purchaseService).getInvoicingAmount(organId); + } + + @Test + @WithMockLoginUser(organId = 1L) + void testGetOrganProductOverview_initialPurchase() { + Long organId = SecurityFrameworkUtils.getUserOrganId(); + + PurchaseRecordDO record = new PurchaseRecordDO(); + record.setEndTime(LocalDate.of(2025, 1, 1)); + record.setInitial(true); + + OrgEarliestPurchaseProductRespVO resp = new OrgEarliestPurchaseProductRespVO(record); + + when(purchaseService.getOrgEarliestProductDetail(organId)) + .thenReturn(resp); + when(purchaseService.getInvoicingAmount(organId)) + .thenReturn(BigDecimal.ZERO); + + OrganProductOverviewRespVO result = organAmountService.getOrganProductOverview(); + + assertNotNull(result); + assertNull(result.getDuration()); + assertNull(result.getDurationUnit()); + } + + @Test + void testGetOrganAmountAll() { + // given + TotalSalesAmountRespVO totalSales = new TotalSalesAmountRespVO(new BigDecimal("9999.99")); + + when(purchaseService.getTotalSales()).thenReturn(totalSales); + + InvoiceAmountRespVO invoiceAmount = new InvoiceAmountRespVO(); + invoiceAmount.setPendingInvoice(new BigDecimal("123.45")); + + when(invoiceService.getManageInvoiceAmount()) + .thenReturn(invoiceAmount); + + // when + ManageAmountOverviewRespVO result = organAmountService.getOrganAmountAll(); + + // then + assertNotNull(result); + assertEquals(new BigDecimal("9999.99"), result.getTotalSalesAmount()); + assertEquals(new BigDecimal("123.45"), result.getToExamineAmount()); + + verify(purchaseService).getTotalSales(); + verify(invoiceService).getManageInvoiceAmount(); + } + + + @Test + @WithMockLoginUser(organId = 1L) + void should_return_consumption_trend_with_zero_filling() { + Long organId = SecurityFrameworkUtils.getUserOrganId(); + + OrganFundsStatisticsReqVO reqVO = new OrganFundsStatisticsReqVO(); + reqVO.setUnit(StatisticsUnit.DAY.getValue()); + reqVO.setCreateTime(new LocalDate[]{ + LocalDate.now().minusDays(2), + LocalDate.now().plusDays(1) + }); + + IncomeExpenseDetailsDO pojo1 = randomPojo(IncomeExpenseDetailsDO.class, o -> { + o.setOrganId(organId); + o.setAmount(new BigDecimal("100")); + o.setCreateTime(LocalDateTime.now().minusDays(2)); + }); + IncomeExpenseDetailsDO pojo2 = randomPojo(IncomeExpenseDetailsDO.class, o -> { + o.setOrganId(organId); + o.setAmount(new BigDecimal("200")); + o.setCreateTime(LocalDateTime.now()); + }); + IncomeExpenseDetailsDO pojo3 = randomPojo(IncomeExpenseDetailsDO.class, o -> { + o.setAmount(new BigDecimal("200")); + o.setCreateTime(LocalDateTime.now()); + }); + + incomeExpenseDetailsMapper.insert(pojo1); + incomeExpenseDetailsMapper.insert(pojo2); + incomeExpenseDetailsMapper.insert(pojo3); + + // when query org + Map result = + organAmountService.getConsumptionTrend(reqVO, true); + + // then + List xAxis = (List) result.get("dateList"); + List series = (List) result.get("series"); + + assertEquals(4, xAxis.size()); + + Map seriesItem = (Map) series.get(0); + BigDecimal[] data = (BigDecimal[]) seriesItem.get("data"); + + assertArrayEquals( + new BigDecimal[]{ + new BigDecimal("100.00"), + BigDecimal.ZERO, + new BigDecimal("200.00"), + BigDecimal.ZERO, + }, + data + ); + + // when not query org + result = organAmountService.getConsumptionTrend(reqVO, false); + + // then + xAxis = (List) result.get("dateList"); + series = (List) result.get("series"); + + assertEquals(4, xAxis.size()); + + seriesItem = (Map) series.get(0); + data = (BigDecimal[]) seriesItem.get("data"); + + assertArrayEquals( + new BigDecimal[]{ + new BigDecimal("100.00"), + BigDecimal.ZERO, + new BigDecimal("400.00"), + BigDecimal.ZERO, + }, + data + ); + } +} diff --git a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/funds/purchase/PurchaseServiceImplTest.java b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/funds/purchase/PurchaseServiceImplTest.java new file mode 100644 index 000000000..2722e3b69 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/funds/purchase/PurchaseServiceImplTest.java @@ -0,0 +1,1135 @@ +package com.cf.imes.module.system.service.funds.purchase; + +import cn.hutool.core.collection.CollUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.cf.imes.framework.common.exception.ServiceException; +import com.cf.imes.framework.common.pojo.PageResult; +import com.cf.imes.framework.test.core.ut.BaseDbAndRedisUnitTest; +import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelayRespVO; +import com.cf.imes.module.system.controller.admin.funds.organamount.vo.TotalSalesAmountRespVO; +import com.cf.imes.module.system.controller.admin.funds.products.vo.productDetails.ProductDetailsRespVO; +import com.cf.imes.module.system.controller.admin.funds.purchase.vo.OrgEarliestPurchaseProductRespVO; +import com.cf.imes.module.system.controller.admin.funds.purchase.vo.PurchaseRecordPageReqVO; +import com.cf.imes.module.system.controller.admin.funds.purchase.vo.PurchaseRecordRespVO; +import com.cf.imes.module.system.controller.admin.funds.purchase.vo.SalesDetailRespVO; +import com.cf.imes.module.system.controller.admin.funds.purchase.vo.SalesDetailsPageReqVO; +import com.cf.imes.module.system.controller.admin.organ.vo.organ.OrganCreateProductPurchaseReqVO; +import com.cf.imes.module.system.controller.admin.statistics.vo.ManageSalesTotalStatisticRespVO; +import com.cf.imes.module.system.dal.dataobject.funds.productpromotion.ProductPromotionDO; +import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDO; +import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDetailDO; +import com.cf.imes.module.system.dal.dataobject.funds.purchase.PurchaseRecordDO; +import com.cf.imes.module.system.dal.dataobject.organ.OrganizationDO; +import com.cf.imes.module.system.dal.mysql.funds.purchase.PurchaseRecordMapper; +import com.cf.imes.module.system.enums.pay.PayChannelCodeEnum; +import com.cf.imes.module.system.enums.pay.ProductDurationUnitEnum; +import com.cf.imes.module.system.enums.pay.PurchaseRecordStatusEnum; +import com.cf.imes.module.system.enums.pay.TradeTypeEnum; +import com.cf.imes.module.system.framework.snowflake.config.MybatisIdProperties; +import com.cf.imes.module.system.service.funds.delay.ProductDelayService; +import com.cf.imes.module.system.service.funds.productpromotion.ProductPromotionService; +import com.cf.imes.module.system.service.funds.products.ProductsDetailService; +import com.cf.imes.module.system.service.organ.OrganService; +import com.cf.imes.framework.common.util.i18n.core.util.I18nUtils; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.bean.override.mockito.MockitoBean; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static com.cf.imes.framework.test.core.util.RandomUtils.randomPojo; +import static com.cf.imes.module.system.enums.ErrorCodeConstants.ORG_PRODUCT_PURCHASE_EXIST_ERROR; +import static com.cf.imes.module.system.enums.ErrorCodeConstants.ORG_PRODUCT_PURCHASE_NOT_EXIST_ERROR; +import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCT_PRICE_NULL_ERROR; +import static com.cf.imes.module.system.enums.pay.InvoiceStatusEnum.INVOICABLE; +import static com.cf.imes.module.system.enums.pay.InvoiceStatusEnum.INVOICINGSUCCESSFUL; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +@Import(PurchaseServiceImpl.class) +class PurchaseServiceImplTest extends BaseDbAndRedisUnitTest { + + @Resource + private PurchaseServiceImpl purchaseService; + + @Resource + private PurchaseRecordMapper purchaseRecordMapper; + + @MockitoBean + private OrganService organService; + + @MockitoBean + private I18nUtils i18nUtils; + + @MockitoBean + private ProductsDetailService productsDetailService; + + @MockitoBean + private ProductDelayService productDelayService; + + @MockitoBean + private ProductPromotionService productPromotionService; + + @MockitoBean + private MybatisIdProperties mybatisIdProperties; + + @Test + void testCreateAndGetAndDeleteRecord() { + // 准备参数 + PurchaseRecordDO invoiceRecordsDO = randomPojo(PurchaseRecordDO.class, o -> { + o.setId(1L); + o.setCreateTime(LocalDateTime.now()); + o.setExternalSpent(new BigDecimal("100.00")); + o.setProductName("测试产品"); + o.setTotalAmount(new BigDecimal("200.00")); + o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode()); + o.setIsInvocing(INVOICABLE.getCode()); + o.setInitial(false); + o.setOrganId(1L); + }); + purchaseService.createRecord(invoiceRecordsDO); + + // 查询 + PurchaseRecordDO result = purchaseService.getRecord(1L); + + // 验证插入 + assertNotNull(result); + assertEquals(invoiceRecordsDO.getProductName(), result.getProductName()); + assertEquals(invoiceRecordsDO.getTotalAmount(), result.getTotalAmount()); + + // 更新为可开票 + purchaseService.updateRecordInvoicable(1L, invoiceRecordsDO.getOrganId()); + + // 查询 + result = purchaseService.getRecord(1L); + + // 校验更新 + assertNotNull(result); + assertEquals(result.getIsInvocing(), INVOICABLE.getCode()); + + // 删除 + purchaseService.deleteRecord(1L); + + // 查询 + result = purchaseService.getRecord(1L); + + // 验证删除 + assertNull(result); + } + + @Test + void testGetPurchaseRecord_emptyResult() { + PurchaseRecordPageReqVO pageReqVO = new PurchaseRecordPageReqVO(); + + PageResult result = + purchaseService.getPurchaseRecord(pageReqVO); + + assertNotNull(result); + assertEquals(0, result.getTotal()); + assertTrue(CollUtil.isEmpty(result.getList())); + } + + @Test + void testGetPurchaseRecord_invalidEndTime_realMapper() { + // 准备参数 + PurchaseRecordDO recordDO = randomPojo(PurchaseRecordDO.class, o -> { + o.setCreateTime(LocalDateTime.now()); + o.setExternalSpent(new BigDecimal("100.00")); + o.setProductName("测试产品"); + o.setTotalAmount(new BigDecimal("200.00")); + o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode()); + o.setIsInvocing(INVOICABLE.getCode()); + o.setInitial(false); + o.setOrganId(1L); + }); + purchaseRecordMapper.insert(recordDO); + + PurchaseRecordPageReqVO pageReqVO = new PurchaseRecordPageReqVO(); + pageReqVO.setEndTime(new LocalDate[]{LocalDate.now()}); // length != 2 + + when(i18nUtils.getMessage(anyString())).thenReturn("支付宝"); + when(organService.getOrganWithDeleted(anyLong())).thenReturn(null); + + PageResult result = + purchaseService.getPurchaseRecord(pageReqVO); + + assertEquals(1L, result.getTotal()); + PurchaseRecordRespVO respVO = result.getList().get(0); + assertEquals("支付宝", respVO.getPaymentMethod()); + assertNull(respVO.getOrganName()); + } + + @Test + void testGetPurchaseRecord_validEndTime_realMapper() { + // 准备参数 + PurchaseRecordDO recordDO = randomPojo(PurchaseRecordDO.class, o -> { + o.setCreateTime(LocalDateTime.now()); + o.setExternalSpent(new BigDecimal("100.00")); + o.setProductName("测试产品"); + o.setTotalAmount(new BigDecimal("200.00")); + o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode()); + o.setIsInvocing(INVOICABLE.getCode()); + o.setInitial(false); + o.setOrganId(2L); + }); + purchaseRecordMapper.insert(recordDO); + + PurchaseRecordPageReqVO pageReqVO = new PurchaseRecordPageReqVO(); + pageReqVO.setEndTime(new LocalDate[]{ + LocalDate.now().minusDays(1), + LocalDate.now() + }); + + when(i18nUtils.getMessage(anyString())).thenReturn("支付宝"); + + OrganizationDO organ = new OrganizationDO(); + organ.setId(2L); + organ.setName("测试组织"); + when(organService.getOrganWithDeleted(2L)).thenReturn(organ); + + PageResult result = + purchaseService.getPurchaseRecord(pageReqVO); + + assertEquals(1L, result.getTotal()); + PurchaseRecordRespVO respVO = result.getList().get(0); + assertEquals("支付宝", respVO.getPaymentMethod()); + assertEquals("测试组织", respVO.getOrganName()); + } + + @Test + void testGetOrgEarliestProductDetail_noPurchaseRecord() { + OrgEarliestPurchaseProductRespVO resp = + purchaseService.getOrgEarliestProductDetail(100L); + + assertNull(resp); + } + + @Test + void testGetOrgEarliestProductDetail_noDelayRecord() { + Long organId = 101L; + + // 准备参数 + PurchaseRecordDO recordDO = randomPojo(PurchaseRecordDO.class, o -> { + o.setCreateTime(LocalDateTime.now()); + o.setExternalSpent(new BigDecimal("100.00")); + o.setProductName("测试产品"); + o.setTotalAmount(new BigDecimal("200.00")); + o.setStatus(PurchaseRecordStatusEnum.ACTIVE.getStatus()); + o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode()); + o.setIsInvocing(INVOICABLE.getCode()); + o.setInitial(false); + o.setOrganId(organId); + o.setEndTime(LocalDate.now().plusDays(30)); + }); + purchaseRecordMapper.insert(recordDO); + + // mock:无延期记录 + when(productDelayService.getRecordsByPurchaseId(recordDO.getId(), organId)) + .thenReturn(Collections.emptyList()); + + OrgEarliestPurchaseProductRespVO resp = + purchaseService.getOrgEarliestProductDetail(organId); + + assertNotNull(resp); + PurchaseRecordDO purchaseRecordDO = resp.getPurchaseRecordDO(); + assertNotNull(purchaseRecordDO); + // endTime 不应被覆盖 + assertEquals(recordDO.getEndTime(), purchaseRecordDO.getEndTime()); + } + + @Test + void testGetOrgEarliestProductDetail_withDelayRecord() { + Long organId = 102L; + + LocalDate originalEndTime = LocalDate.now().plusDays(30); + LocalDate delayEndTime = LocalDate.now().plusDays(60); + + // 准备参数 + PurchaseRecordDO recordDO = randomPojo(PurchaseRecordDO.class, o -> { + o.setCreateTime(LocalDateTime.now()); + o.setExternalSpent(new BigDecimal("100.00")); + o.setProductName("测试产品"); + o.setTotalAmount(new BigDecimal("200.00")); + o.setStatus(PurchaseRecordStatusEnum.ACTIVE.getStatus()); + o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode()); + o.setIsInvocing(INVOICABLE.getCode()); + o.setInitial(false); + o.setOrganId(organId); + o.setEndTime(originalEndTime); + }); + purchaseRecordMapper.insert(recordDO); + + // mock 延期记录(第一条为最新) + ProductDelayRespVO delayVO = new ProductDelayRespVO(); + delayVO.setDelayTime(delayEndTime); + + when(productDelayService.getRecordsByPurchaseId(recordDO.getId(), organId)) + .thenReturn(Collections.singletonList(delayVO)); + + OrgEarliestPurchaseProductRespVO resp = + purchaseService.getOrgEarliestProductDetail(organId); + + assertNotNull(resp); + PurchaseRecordDO purchaseRecordDO = resp.getPurchaseRecordDO(); + assertNotNull(purchaseRecordDO); + // endTime 应被延期时间覆盖 + assertEquals(delayEndTime, purchaseRecordDO.getEndTime()); + } + + @Test + void getInvoicingAmountTest(){ + // 准备参数 + PurchaseRecordDO recordDO = randomPojo(PurchaseRecordDO.class, o -> { + o.setCreateTime(LocalDateTime.now()); + o.setExternalSpent(new BigDecimal("100.00")); + o.setProductName("测试产品"); + o.setTotalAmount(new BigDecimal("200.00")); + o.setStatus(PurchaseRecordStatusEnum.ACTIVE.getStatus()); + o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode()); + o.setIsInvocing(INVOICABLE.getCode()); + o.setInitial(false); + o.setOrganId(1L); + }); + purchaseRecordMapper.insert(recordDO); + + BigDecimal invoicingAmount = purchaseService.getInvoicingAmount(1l); + assertEquals(recordDO.getTotalAmount(), invoicingAmount); + } + + @Test + void testCreateOrgInitProductPurchaseRecord_single() { + Long organId = 1L; + Long productId = 10L; + + BigDecimal price = new BigDecimal("1000.00"); + + // 请求参数 + OrganCreateProductPurchaseReqVO reqVO = new OrganCreateProductPurchaseReqVO(); + reqVO.setProductId(productId); + reqVO.setPrice(price); + + // mock 产品存在 + ProductsDO productsDO = new ProductsDO(); + productsDO.setId(productId); + productsDO.setProductName("测试产品"); + + when(productsDetailService.validateProductExist(productId)) + .thenReturn(productsDO); + + // mock 雪花 ID 参数 + when(mybatisIdProperties.getWorkerId()).thenReturn(1L); + when(mybatisIdProperties.getDatacenterId()).thenReturn(1L); + + // 执行 + purchaseService.createOrgInitProductPurchaseRecord( + organId, Collections.singletonList(reqVO) + ); + + // 查询数据库验证 + List records = purchaseRecordMapper.selectList( + new LambdaQueryWrapper() + .eq(PurchaseRecordDO::getOrganId, organId) + .eq(PurchaseRecordDO::getProductId, productId) + ); + + assertEquals(1, records.size()); + + PurchaseRecordDO record = records.get(0); + + // 断言核心字段 + assertTrue(record.getId() > 0); + assertEquals("测试产品", record.getProductName()); + assertEquals(price, record.getTotalAmount()); + assertEquals(price, record.getOriginalAmount()); + + // monthAverageAmount = price / 10 + assertEquals(new BigDecimal("100.00"), record.getMonthAverageAmount()); + + // 初始化语义 + assertTrue(record.getInitial()); + assertEquals(1, record.getPurchaseDuration()); + assertEquals(ProductDurationUnitEnum.YEAR.getCode(), record.getPurchaseDurationUnit()); + assertEquals(PurchaseRecordStatusEnum.ACTIVE.getStatus(), record.getStatus()); + + // 过期时间 + assertEquals(LocalDate.now().minusDays(1), record.getEndTime()); + } + + @Test + void testCreateOrgInitProductPurchaseRecord_empty() { + purchaseService.createOrgInitProductPurchaseRecord( + null, Collections.emptyList() + ); + + // 查询数据库验证 + List records = purchaseRecordMapper.selectList(); + + assertEquals(0, records.size()); + } + + @Test + void testUpdateOrgInitProductPurchaseRecord_updateNotExist() { + Long organId = 1L; + + // DB 中已有首购记录 + PurchaseRecordDO record = randomPojo(PurchaseRecordDO.class, o -> { + o.setCreateTime(LocalDateTime.now()); + o.setExternalSpent(new BigDecimal("100.00")); + o.setProductName("测试产品"); + o.setTotalAmount(new BigDecimal("200.00")); + o.setStatus(PurchaseRecordStatusEnum.ACTIVE.getStatus()); + o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode()); + o.setIsInvocing(INVOICABLE.getCode()); + o.setInitial(false); + o.setOrganId(organId); + }); + purchaseRecordMapper.insert(record); + + // 入参:updateList 中的 purchaseId 不存在 + OrganCreateProductPurchaseReqVO reqVO = new OrganCreateProductPurchaseReqVO(); + reqVO.setPurchaseId(9999L); + reqVO.setPrice(new BigDecimal("1000")); + + ServiceException ex = assertThrows(ServiceException.class, () -> + purchaseService.updateOrgInitProductPurchaseRecord( + organId, Collections.singletonList(reqVO) + ) + ); + + assertEquals(ORG_PRODUCT_PURCHASE_NOT_EXIST_ERROR.getCode(), ex.getCode()); + } + + @Test + void testUpdateOrgInitProductPurchaseRecord_duplicateCreate() { + Long organId = 2L; + Long productId = 10L; + + // DB 中已有首购记录 + PurchaseRecordDO record = randomPojo(PurchaseRecordDO.class, o -> { + o.setCreateTime(LocalDateTime.now()); + o.setExternalSpent(new BigDecimal("100.00")); + o.setProductName("测试产品"); + o.setTotalAmount(new BigDecimal("200.00")); + o.setStatus(PurchaseRecordStatusEnum.ACTIVE.getStatus()); + o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode()); + o.setIsInvocing(INVOICABLE.getCode()); + o.setInitial(true); + o.setOrganId(organId); + o.setProductId(productId); + }); + purchaseRecordMapper.insert(record); + + // 新增请求(purchaseId = null) + OrganCreateProductPurchaseReqVO reqVO = new OrganCreateProductPurchaseReqVO(); + reqVO.setProductId(productId); + reqVO.setPrice(new BigDecimal("500")); + + ServiceException ex = assertThrows(ServiceException.class, () -> + purchaseService.updateOrgInitProductPurchaseRecord( + organId, Collections.singletonList(reqVO) + ) + ); + + assertEquals(ORG_PRODUCT_PURCHASE_EXIST_ERROR.getCode(), ex.getCode()); + } + + @Test + void testUpdateOrgInitProductPurchaseRecord_success() { + Long organId = 3L; + + // DB 中已有两条首购记录 + PurchaseRecordDO record1 = randomPojo(PurchaseRecordDO.class, o -> { + o.setId(3001L); + o.setOrganId(organId); + o.setProductId(101L); + o.setProductName("产品1"); + o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode()); + o.setIsInvocing(INVOICABLE.getCode()); + o.setInitial(true); + o.setDeleted(false); + }); + + PurchaseRecordDO record2 = randomPojo(PurchaseRecordDO.class, o -> { + o.setId(3002L); + o.setOrganId(organId); + o.setProductId(102L); + o.setProductName("产品2"); + o.setPaymentMethod(PayChannelCodeEnum.WECHAT_NATIVE.getCode()); + o.setIsInvocing(INVOICABLE.getCode()); + o.setInitial(true); + o.setDeleted(false); + }); + + purchaseRecordMapper.insert(record1); + purchaseRecordMapper.insert(record2); + + // update 请求 + OrganCreateProductPurchaseReqVO updateReq = new OrganCreateProductPurchaseReqVO(); + updateReq.setPurchaseId(3001L); + updateReq.setPrice(new BigDecimal("1000")); + + // create 请求 + OrganCreateProductPurchaseReqVO createReq = new OrganCreateProductPurchaseReqVO(); + createReq.setProductId(103L); + createReq.setPrice(new BigDecimal("600")); + + // mock 产品校验 + ProductsDO productsDO = new ProductsDO(); + productsDO.setId(103L); + productsDO.setProductName("产品3"); + + when(productsDetailService.validateProductExist(103L)) + .thenReturn(productsDO); + + when(mybatisIdProperties.getWorkerId()).thenReturn(1L); + when(mybatisIdProperties.getDatacenterId()).thenReturn(1L); + + // 执行 + purchaseService.updateOrgInitProductPurchaseRecord( + organId, Arrays.asList(updateReq, createReq) + ); + + // 验证 update + PurchaseRecordDO updated = purchaseRecordMapper.selectById(3001L); + assertEquals(new BigDecimal("1000.00"), updated.getTotalAmount()); + assertEquals(new BigDecimal("100.00"), updated.getMonthAverageAmount()); + + // 验证 delete(3002 被删) + assertNull(purchaseRecordMapper.selectById(3002L)); + + // 验证 create + List list = purchaseRecordMapper.selectList( + new LambdaQueryWrapper() + .eq(PurchaseRecordDO::getOrganId, organId) + .eq(PurchaseRecordDO::getProductId, 103L) + ); + assertEquals(1, list.size()); + } + + @Test + void testGetOrgPurchaseList() { + Long organId1 = 1L; + Long otherOrganId = 99L; + + LocalDateTime now = LocalDateTime.now(); + + // organId1 - 最新 + PurchaseRecordDO r1 = randomPojo(PurchaseRecordDO.class, o -> { + o.setId(1001L); + o.setOrganId(organId1); + o.setDeleted(false); + o.setCreateTime(now); + o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode()); + o.setIsInvocing(INVOICABLE.getCode()); + }); + + // deleted = true(应被过滤) + PurchaseRecordDO r2 = randomPojo(PurchaseRecordDO.class, o -> { + o.setId(1004L); + o.setOrganId(organId1); + o.setDeleted(true); + o.setCreateTime(now.plusDays(1)); + o.setPaymentMethod(PayChannelCodeEnum.WECHAT_NATIVE.getCode()); + o.setIsInvocing(INVOICABLE.getCode()); + }); + + // organId 不在集合内(应被过滤) + PurchaseRecordDO r3 = randomPojo(PurchaseRecordDO.class, o -> { + o.setId(1005L); + o.setOrganId(otherOrganId); + o.setDeleted(false); + o.setCreateTime(now.plusDays(1)); + o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode()); + o.setIsInvocing(INVOICABLE.getCode()); + }); + + purchaseRecordMapper.insert(r1); + purchaseRecordMapper.insert(r2); + purchaseRecordMapper.insert(r3); + + Set organIds = new HashSet<>(Arrays.asList(organId1)); + + List list = purchaseService.getOrgPurchaseList(organIds); + + // 只剩 1 条 + assertEquals(1, list.size()); + } + + @Test + public void testGetTotalSales() { + // 准备数据 + PurchaseRecordDO r1 = randomPojo(PurchaseRecordDO.class, o -> { + o.setId(2001L); + o.setDeleted(false); + o.setTotalAmount(new BigDecimal("100.00")); + o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode()); + o.setIsInvocing(INVOICABLE.getCode()); + o.setInitial(false); + }); + + PurchaseRecordDO r2 = randomPojo(PurchaseRecordDO.class, o -> { + o.setId(2002L); + o.setDeleted(false); + o.setTotalAmount(new BigDecimal("300.00")); + o.setPaymentMethod(PayChannelCodeEnum.WECHAT_NATIVE.getCode()); + o.setIsInvocing(INVOICINGSUCCESSFUL.getCode()); + o.setInitial(false); + }); + + purchaseRecordMapper.insert(r1); + purchaseRecordMapper.insert(r2); + + TotalSalesAmountRespVO resp = purchaseService.getTotalSales(); + + assertNotNull(resp); + assertEquals(new BigDecimal("400.00"), resp.getAmount()); + } + + @Test + public void testGetManageSalesDetailsPage_success() { + SalesDetailsPageReqVO reqVO = new SalesDetailsPageReqVO(); + + Long organId = 1L; + + PurchaseRecordDO recordDO = randomPojo(PurchaseRecordDO.class, o -> { + o.setId(1001L); + o.setOrganId(organId); + o.setExternalSpent(new BigDecimal("200.00")); + o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode()); + o.setCreateTime(LocalDateTime.now()); + o.setIsInvocing(INVOICINGSUCCESSFUL.getCode()); + o.setInitial(false); + }); + + purchaseRecordMapper.insert(recordDO); + + // mock 国际化 + when(i18nUtils.getMessage(anyString())).thenReturn("支付宝"); + + // mock 组织 + OrganizationDO organ = new OrganizationDO(); + organ.setId(organId); + organ.setName("测试组织"); + + when(organService.getOrganWithDeleted(organId)).thenReturn(organ); + + PageResult pageResult = + purchaseService.getManageSalesDetailsPage(reqVO); + + assertEquals(1L, pageResult.getTotal()); + assertEquals(1, pageResult.getList().size()); + + SalesDetailRespVO respVO = pageResult.getList().get(0); + + assertEquals(TradeTypeEnum.PRODUCT.getCode(), respVO.getTradeType()); + assertEquals(new BigDecimal("200.00"), respVO.getRechargeSpent()); + assertEquals("支付宝", respVO.getPaymentMethod()); + assertEquals("测试组织", respVO.getOrganName()); + } + + @Test + public void testGetManageSalesDetailsPage_organNotExist() { + SalesDetailsPageReqVO reqVO = new SalesDetailsPageReqVO(); + reqVO.setPageNo(1); + reqVO.setPageSize(10); + + Long organId = 2L; + + PurchaseRecordDO recordDO = randomPojo(PurchaseRecordDO.class, o -> { + o.setId(1002L); + o.setOrganId(organId); + o.setExternalSpent(new BigDecimal("100.00")); + o.setPaymentMethod(PayChannelCodeEnum.WECHAT_NATIVE.getCode()); + o.setCreateTime(LocalDateTime.now()); + o.setIsInvocing(INVOICINGSUCCESSFUL.getCode()); + o.setInitial(false); + }); + + purchaseRecordMapper.insert(recordDO); + + when(i18nUtils.getMessage(anyString())).thenReturn("微信"); + when(organService.getOrganWithDeleted(organId)).thenReturn(null); + + PageResult pageResult = + purchaseService.getManageSalesDetailsPage(reqVO); + + SalesDetailRespVO respVO = pageResult.getList().get(0); + + assertEquals("微信", respVO.getPaymentMethod()); + assertNull(respVO.getOrganName()); + } + + @Test + public void testGetOrgInitialPurchaseRecord_success() { + Long organId = 1L; + Long productId = 100L; + + PurchaseRecordDO initialRecord = randomPojo(PurchaseRecordDO.class, o -> { + o.setId(1001L); + o.setOrganId(organId); + o.setProductId(productId); + o.setInitial(true); + o.setDeleted(false); + o.setPaymentMethod(PayChannelCodeEnum.WECHAT_NATIVE.getCode()); + o.setIsInvocing(INVOICINGSUCCESSFUL.getCode()); + }); + + purchaseRecordMapper.insert(initialRecord); + + PurchaseRecordDO result = + purchaseService.getOrgInitialPurchaseRecord(organId, productId); + + assertNotNull(result); + assertEquals(initialRecord.getId(), result.getId()); + assertTrue(result.getInitial()); + } + + @Test + public void testGetOrgProductPendingPurchaseList_success() { + Long organId = 3L; + Long productId = 300L; + + PurchaseRecordDO pending1 = randomPojo(PurchaseRecordDO.class, o -> { + o.setId(2001L); + o.setOrganId(organId); + o.setProductId(productId); + o.setStatus(PurchaseRecordStatusEnum.PENDING.getStatus()); + o.setDeleted(false); + o.setPaymentMethod(PayChannelCodeEnum.WECHAT_NATIVE.getCode()); + o.setIsInvocing(INVOICINGSUCCESSFUL.getCode()); + }); + + // 干扰数据 + PurchaseRecordDO active = randomPojo(PurchaseRecordDO.class, o -> { + o.setOrganId(organId); + o.setProductId(productId); + o.setStatus(PurchaseRecordStatusEnum.ACTIVE.getStatus()); + o.setDeleted(false); + o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode()); + o.setIsInvocing(INVOICINGSUCCESSFUL.getCode()); + }); + + purchaseRecordMapper.insert(pending1); + purchaseRecordMapper.insert(active); + + List result = + purchaseService.getOrgProductPendingPurchaseList(organId, productId); + + assertEquals(1, result.size()); + assertTrue(result.stream() + .allMatch(r -> PurchaseRecordStatusEnum.PENDING.getStatus().equals(r.getStatus()))); + } + + @Test + void test_noInitialPurchase_withYearPricing_noPromotion() { + Long PRODUCT_ID = 100L; + Long ORGAN_ID = 1L; + + ProductsDO product = new ProductsDO(); + product.setId(PRODUCT_ID); + product.setPrice(new BigDecimal("1200")); + + when(productsDetailService.validateProductExist(PRODUCT_ID)) + .thenReturn(product); + + ProductsDetailDO yearDetail = new ProductsDetailDO(); + yearDetail.setDuration(1); + yearDetail.setDurationUnit(ProductDurationUnitEnum.YEAR.getCode()); + yearDetail.setDiscount(new BigDecimal("80")); + + when(productsDetailService.getPage(any())) + .thenReturn(new PageResult<>(List.of(yearDetail), 1L)); + + when(productPromotionService.getAvailableProductPromotionList(PRODUCT_ID)) + .thenReturn(Collections.emptyList()); + + List result = + purchaseService.getOrgProductDetailList(ORGAN_ID, PRODUCT_ID); + + // 10个月 + 1年 + assertEquals(11, result.size()); + + ProductDetailsRespVO year = + result.stream() + .filter(r -> r.getDurationUnit().equals(ProductDurationUnitEnum.YEAR.getCode())) + .findFirst().orElseThrow(); + + assertEquals(new BigDecimal("1200"), year.getOriginalPrice()); + assertEquals(new BigDecimal("960.00"), year.getCurrentPrice()); + assertNull(year.getGiftDuration()); + } + + @Test + void test_withInitialPurchase_withYearPricing_withPromotion() { + Long PRODUCT_ID = 100L; + Long ORGAN_ID = 1L; + + ProductsDO product = new ProductsDO(); + product.setId(PRODUCT_ID); + product.setPrice(new BigDecimal("999")); + + when(productsDetailService.validateProductExist(PRODUCT_ID)) + .thenReturn(product); + + ProductsDetailDO yearDetail = new ProductsDetailDO(); + yearDetail.setDuration(2); + yearDetail.setDurationUnit(ProductDurationUnitEnum.YEAR.getCode()); + yearDetail.setDiscount(new BigDecimal("50")); + + when(productsDetailService.getPage(any())) + .thenReturn(new PageResult<>(List.of(yearDetail), 1L)); + + ProductPromotionDO promotion = new ProductPromotionDO(); + promotion.setPurchaseDuration(2); + promotion.setPurchaseDurationUnit(ProductDurationUnitEnum.YEAR.getCode()); + promotion.setGiftDuration(3); + promotion.setGiftDurationUnit(ProductDurationUnitEnum.MONTH.getCode()); + + when(productPromotionService.getAvailableProductPromotionList(PRODUCT_ID)) + .thenReturn(List.of(promotion)); + + PurchaseRecordDO record = new PurchaseRecordDO(); + record.setTotalAmount(new BigDecimal("1000")); + record.setMonthAverageAmount(new BigDecimal("100")); + + ProductDetailsRespVO year = + purchaseService.getOrgProductDetailList(ORGAN_ID, PRODUCT_ID) + .stream() + .filter(r -> r.getDurationUnit().equals(ProductDurationUnitEnum.YEAR.getCode())) + .findFirst().orElseThrow(); + + // 原价:1000 * 2 + assertEquals(new BigDecimal("1998"), year.getOriginalPrice()); + assertEquals(new BigDecimal("999.00"), year.getCurrentPrice()); + + // promotion 命中 + assertEquals(3, year.getGiftDuration()); + assertEquals(ProductDurationUnitEnum.MONTH.getCode(), year.getGiftDurationUnit()); + } + + @Test + void test_withInitialPurchase_withoutYearPricing_autoFillYear() { + Long PRODUCT_ID = 100L; + Long ORGAN_ID = 1L; + + ProductsDO product = new ProductsDO(); + product.setId(PRODUCT_ID); + product.setPrice(new BigDecimal("888")); + + when(productsDetailService.validateProductExist(PRODUCT_ID)) + .thenReturn(product); + + // 只有 MONTH + ProductsDetailDO monthDetail = new ProductsDetailDO(); + monthDetail.setDuration(6); + monthDetail.setDurationUnit(ProductDurationUnitEnum.MONTH.getCode()); + + when(productsDetailService.getPage(any())) + .thenReturn(new PageResult<>(List.of(monthDetail), 1L)); + + when(productPromotionService.getAvailableProductPromotionList(PRODUCT_ID)) + .thenReturn(Collections.emptyList()); + + PurchaseRecordDO record = new PurchaseRecordDO(); + record.setTotalAmount(new BigDecimal("888")); + record.setMonthAverageAmount(new BigDecimal("88.8")); + + List result = + purchaseService.getOrgProductDetailList(ORGAN_ID, PRODUCT_ID); + + ProductDetailsRespVO year = + result.stream() + .filter(r -> r.getDurationUnit().equals(ProductDurationUnitEnum.YEAR.getCode())) + .findFirst().orElseThrow(); + + assertEquals(new BigDecimal("888"), year.getCurrentPrice()); + } + + + @Test + void test_getInitialProductProductPriceNullError(){ + Long PRODUCT_ID = 100L; + Long ORGAN_ID = 1L; + + ProductsDO product = new ProductsDO(); + product.setId(PRODUCT_ID); + + when(productsDetailService.validateProductExist(PRODUCT_ID)) + .thenReturn(product); + + when(productsDetailService.getPage(any())) + .thenReturn(new PageResult<>(anyList(), 1L)); + + ServiceException ex = assertThrows(ServiceException.class, () -> + purchaseService.getOrgProductDetailList(ORGAN_ID, PRODUCT_ID) + ); + + assertEquals(PRODUCT_PRICE_NULL_ERROR.getCode(), ex.getCode()); + } + + @Test + void test_getOrgProductDetailList_WithOrgInitial() { + Long PRODUCT_ID = 100L; + Long ORGAN_ID = 1L; + + ProductsDO product = new ProductsDO(); + product.setId(PRODUCT_ID); + product.setPrice(new BigDecimal("1000")); + + when(productsDetailService.validateProductExist(PRODUCT_ID)) + .thenReturn(product); + + ProductsDetailDO yearDetail = new ProductsDetailDO(); + yearDetail.setDuration(2); + yearDetail.setDurationUnit(ProductDurationUnitEnum.YEAR.getCode()); + yearDetail.setDiscount(new BigDecimal("60")); + when(productsDetailService.getPage(any())) + .thenReturn(new PageResult<>(List.of(yearDetail), 1L)); + + ProductPromotionDO productPromotionDO = new ProductPromotionDO(); + productPromotionDO.setPurchaseDuration(2); + productPromotionDO.setPurchaseDurationUnit(ProductDurationUnitEnum.YEAR.getCode()); + // 优惠活动 + when(productPromotionService.getAvailableProductPromotionList(PRODUCT_ID)) + .thenReturn(Collections.singletonList(productPromotionDO)); + + // 插入首购记录 + PurchaseRecordDO recordDO = randomPojo(PurchaseRecordDO.class, o -> { + o.setOrganId(ORGAN_ID); + o.setProductId(PRODUCT_ID); + o.setStatus(PurchaseRecordStatusEnum.PENDING.getStatus()); + o.setDeleted(false); + o.setPaymentMethod(PayChannelCodeEnum.WECHAT_NATIVE.getCode()); + o.setIsInvocing(INVOICINGSUCCESSFUL.getCode()); + o.setInitial(true); + o.setMonthAverageAmount(new BigDecimal("99")); + o.setTotalAmount(new BigDecimal("999")); + }); + purchaseRecordMapper.insert(recordDO); + + List result = + purchaseService.getOrgProductDetailList(ORGAN_ID, PRODUCT_ID); + + ProductDetailsRespVO year = + result.stream() + .filter(r -> r.getDurationUnit().equals(ProductDurationUnitEnum.YEAR.getCode())) + .findFirst().orElseThrow(); + + assertEquals(new BigDecimal("1198.80"), year.getCurrentPrice()); + assertEquals(new BigDecimal("1998.00"), year.getOriginalPrice()); + } + + @Test + void test_getOrgProductDetailList_WithYear() { + Long PRODUCT_ID = 100L; + Long ORGAN_ID = 1L; + + ProductsDO product = new ProductsDO(); + product.setId(PRODUCT_ID); + product.setPrice(new BigDecimal("1000")); + + when(productsDetailService.validateProductExist(PRODUCT_ID)) + .thenReturn(product); + + ProductsDetailDO yearDetail = new ProductsDetailDO(); + yearDetail.setDuration(2); + yearDetail.setDurationUnit(ProductDurationUnitEnum.YEAR.getCode()); + yearDetail.setDiscount(new BigDecimal("60")); + when(productsDetailService.getPage(any())) + .thenReturn(new PageResult<>(List.of(yearDetail), 1L)); + + ProductPromotionDO productPromotionDO = new ProductPromotionDO(); + productPromotionDO.setPurchaseDuration(2); + productPromotionDO.setPurchaseDurationUnit(ProductDurationUnitEnum.YEAR.getCode()); + // 优惠活动 + when(productPromotionService.getAvailableProductPromotionList(PRODUCT_ID)) + .thenReturn(Collections.singletonList(productPromotionDO)); + + // 插入首购记录 + PurchaseRecordDO recordDO = randomPojo(PurchaseRecordDO.class, o -> { + o.setOrganId(ORGAN_ID); + o.setProductId(PRODUCT_ID); + o.setStatus(PurchaseRecordStatusEnum.PENDING.getStatus()); + o.setDeleted(false); + o.setPaymentMethod(PayChannelCodeEnum.WECHAT_NATIVE.getCode()); + o.setIsInvocing(INVOICINGSUCCESSFUL.getCode()); + o.setInitial(true); + o.setMonthAverageAmount(new BigDecimal("99")); + o.setTotalAmount(new BigDecimal("999")); + }); + purchaseRecordMapper.insert(recordDO); + + List result = + purchaseService.getOrgProductDetailList(ORGAN_ID, PRODUCT_ID); + + ProductDetailsRespVO year = + result.stream() + .filter(r -> r.getDurationUnit().equals(ProductDurationUnitEnum.YEAR.getCode())) + .findFirst().orElseThrow(); + + assertEquals(new BigDecimal("1198.80"), year.getCurrentPrice()); + assertEquals(new BigDecimal("1998.00"), year.getOriginalPrice()); + } + + @Test + void test_getOrgProductDetailList_WithoutYear() { + Long PRODUCT_ID = 100L; + Long ORGAN_ID = 1L; + + ProductsDO product = new ProductsDO(); + product.setId(PRODUCT_ID); + product.setPrice(new BigDecimal("1000")); + + when(productsDetailService.validateProductExist(PRODUCT_ID)) + .thenReturn(product); + + ProductsDetailDO monthDetail = new ProductsDetailDO(); + monthDetail.setDuration(2); + monthDetail.setDurationUnit(ProductDurationUnitEnum.MONTH.getCode()); + when(productsDetailService.getPage(any())) + .thenReturn(new PageResult<>(List.of(monthDetail), 1L)); + + ProductPromotionDO productPromotionDO = new ProductPromotionDO(); + productPromotionDO.setPurchaseDuration(2); + productPromotionDO.setPurchaseDurationUnit(ProductDurationUnitEnum.YEAR.getCode()); + // 优惠活动 + when(productPromotionService.getAvailableProductPromotionList(PRODUCT_ID)) + .thenReturn(Collections.singletonList(productPromotionDO)); + + // 插入首购记录 + PurchaseRecordDO recordDO = randomPojo(PurchaseRecordDO.class, o -> { + o.setOrganId(ORGAN_ID); + o.setProductId(PRODUCT_ID); + o.setStatus(PurchaseRecordStatusEnum.PENDING.getStatus()); + o.setDeleted(false); + o.setPaymentMethod(PayChannelCodeEnum.WECHAT_NATIVE.getCode()); + o.setIsInvocing(INVOICINGSUCCESSFUL.getCode()); + o.setInitial(true); + o.setMonthAverageAmount(new BigDecimal("99")); + o.setTotalAmount(new BigDecimal("999")); + }); + purchaseRecordMapper.insert(recordDO); + + List result = + purchaseService.getOrgProductDetailList(ORGAN_ID, PRODUCT_ID); + + ProductDetailsRespVO year = + result.stream() + .filter(r -> r.getDurationUnit().equals(ProductDurationUnitEnum.YEAR.getCode())) + .findFirst().orElseThrow(); + + assertEquals(1, year.getDuration()); + assertEquals(new BigDecimal("1000"), year.getCurrentPrice()); + assertEquals(new BigDecimal("1000"), year.getOriginalPrice()); + } + + @Test + void should_not_update_when_status_is_not_active_or_pending() { + Long organId = 1L; + Long recordId = 101L; + + PurchaseRecordDO recordDO = randomPojo(PurchaseRecordDO.class, o -> { + o.setId(recordId); + o.setOrganId(organId); + o.setStatus(PurchaseRecordStatusEnum.PENDING.getStatus()); + o.setDeleted(false); + o.setPaymentMethod(PayChannelCodeEnum.WECHAT_NATIVE.getCode()); + o.setIsInvocing(INVOICINGSUCCESSFUL.getCode()); + o.setInitial(true); + o.setMonthAverageAmount(new BigDecimal("99")); + o.setTotalAmount(new BigDecimal("999")); + }); + + purchaseRecordMapper.insert(recordDO); + + purchaseService.invalidHistoryOrganProductPurchaseRecord(organId, recordId); + + PurchaseRecordDO updated = + purchaseRecordMapper.selectById(recordId); + + assertEquals(PurchaseRecordStatusEnum.INACTIVE.getStatus(), updated.getStatus()); + } + + @Test + void should_activate_record_when_status_is_pending_and_not_deleted() { + Long organId = 1L; + Long recordId = 200L; + + PurchaseRecordDO record = randomPojo(PurchaseRecordDO.class, o -> { + o.setId(recordId); + o.setOrganId(organId); + o.setStatus(PurchaseRecordStatusEnum.PENDING.getStatus()); + o.setDeleted(false); + o.setPaymentMethod(PayChannelCodeEnum.WECHAT_NATIVE.getCode()); + o.setIsInvocing(INVOICINGSUCCESSFUL.getCode()); + o.setInitial(true); + o.setMonthAverageAmount(new BigDecimal("99")); + o.setTotalAmount(new BigDecimal("999")); + }); + + purchaseRecordMapper.insert(record); + + // when + purchaseService.activateHistoryOrganProductPurchaseRecord(organId, recordId); + + // then + PurchaseRecordDO updated = purchaseRecordMapper.selectById(recordId); + + assertEquals( + PurchaseRecordStatusEnum.ACTIVE.getStatus(), + updated.getStatus() + ); + } + + @Test + void should_return_correct_sales_statistics() { + Long organId = 1L; + + PurchaseRecordDO record1 = randomPojo(PurchaseRecordDO.class, o -> { + o.setOrganId(organId); + o.setStatus(PurchaseRecordStatusEnum.PENDING.getStatus()); + o.setDeleted(false); + o.setPaymentMethod(PayChannelCodeEnum.WECHAT_NATIVE.getCode()); + o.setIsInvocing(INVOICINGSUCCESSFUL.getCode()); + o.setInitial(false); + o.setMonthAverageAmount(new BigDecimal("99")); + o.setTotalAmount(new BigDecimal("200")); + o.setCreateTime(LocalDateTime.now().minusDays(2)); + }); + + PurchaseRecordDO record2 = randomPojo(PurchaseRecordDO.class, o -> { + o.setOrganId(organId); + o.setStatus(PurchaseRecordStatusEnum.PENDING.getStatus()); + o.setDeleted(false); + o.setPaymentMethod(PayChannelCodeEnum.WECHAT_NATIVE.getCode()); + o.setIsInvocing(INVOICINGSUCCESSFUL.getCode()); + o.setInitial(false); + o.setTotalAmount(new BigDecimal("100")); + o.setCreateTime(LocalDateTime.now()); + }); + purchaseRecordMapper.insert(record1); + purchaseRecordMapper.insert(record2); + + // when + ManageSalesTotalStatisticRespVO resp = + purchaseService.getSalesTotalStatistics(); + + // then + assertAll( + () -> assertEquals(new BigDecimal("300.00"), resp.getSalesTotal()), + () -> assertEquals(new BigDecimal("100.00"), resp.getSalesToday()) + ); + } +} \ No newline at end of file