Skip to content

Commit 97cf67c

Browse files
authored
Merge pull request #32 from touchmegit1/shift
feat: Refactor package structure for shift-related services and controllers; enhance ProductVariantService with fallback logic and native query for variant summaries
2 parents 2a44125 + 0abe946 commit 97cf67c

File tree

11 files changed

+165
-17
lines changed

11 files changed

+165
-17
lines changed

.github/workflows/cd.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,12 @@ jobs:
391391
if [ "${VARIANTS_API_COUNT:-0}" = "0" ]; then
392392
echo "ERROR: /api/products/variants returned empty response after deployment."
393393
echo "Last variants response: $VARIANTS_RESPONSE"
394+
395+
UNITS_COUNT="$(get_table_count units || echo 0)"
396+
ACTIVE_VARIANTS_DB_COUNT="$(compose_cmd exec -T mysql mysql -N -s -uroot -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" -e "SELECT COUNT(*) FROM product_variants WHERE is_active = 1;" 2>/dev/null | tr -d '\r' || echo 0)"
397+
JOINABLE_VARIANTS_DB_COUNT="$(compose_cmd exec -T mysql mysql -N -s -uroot -p"$MYSQL_ROOT_PASSWORD" "$MYSQL_DATABASE" -e "SELECT COUNT(*) FROM product_variants pv LEFT JOIN products p ON p.id = pv.product_id LEFT JOIN units u ON u.id = pv.unit_id WHERE p.id IS NOT NULL AND u.id IS NOT NULL;" 2>/dev/null | tr -d '\r' || echo 0)"
398+
399+
echo "Diagnostics: units=$UNITS_COUNT, active_variants=$ACTIVE_VARIANTS_DB_COUNT, joinable_variants=$JOINABLE_VARIANTS_DB_COUNT"
394400
compose_cmd logs --tail=150 backend || true
395401
exit 1
396402
fi

backend/src/main/java/com/smalltrend/controller/ShiftController.java renamed to backend/src/main/java/com/smalltrend/controller/shift/ShiftController.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.smalltrend.controller;
1+
package com.smalltrend.controller.shift;
22

33
import com.smalltrend.dto.common.MessageResponse;
44
import com.smalltrend.dto.shift.AttendanceResponse;
@@ -9,9 +9,9 @@
99
import com.smalltrend.dto.shift.ShiftAssignmentResponse;
1010
import com.smalltrend.dto.shift.WorkShiftRequest;
1111
import com.smalltrend.dto.shift.WorkShiftResponse;
12-
import com.smalltrend.service.ShiftWorkforceService;
13-
import com.smalltrend.service.WorkShiftAssignmentService;
14-
import com.smalltrend.service.WorkShiftService;
12+
import com.smalltrend.service.shift.ShiftWorkforceService;
13+
import com.smalltrend.service.shift.WorkShiftAssignmentService;
14+
import com.smalltrend.service.shift.WorkShiftService;
1515
import com.smalltrend.validation.ShiftValidator;
1616
import jakarta.validation.Valid;
1717
import lombok.RequiredArgsConstructor;

backend/src/main/java/com/smalltrend/entity/ProductVariant.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import lombok.Data;
88
import lombok.NoArgsConstructor;
99
import org.hibernate.annotations.CreationTimestamp;
10+
import org.hibernate.annotations.NotFound;
11+
import org.hibernate.annotations.NotFoundAction;
1012
import org.hibernate.annotations.UpdateTimestamp;
1113

1214
import java.math.BigDecimal;
@@ -26,8 +28,9 @@ public class ProductVariant {
2628
@GeneratedValue(strategy = GenerationType.IDENTITY)
2729
private Integer id;
2830

29-
@ManyToOne
31+
@ManyToOne(fetch = FetchType.LAZY, optional = true)
3032
@JoinColumn(name = "product_id", nullable = false)
33+
@NotFound(action = NotFoundAction.IGNORE)
3134
@JsonIgnore
3235
private Product product;
3336
@Column(unique = true)
@@ -39,8 +42,9 @@ public class ProductVariant {
3942
@Column(name = "plu_code", length = 5)
4043
private String pluCode;
4144

42-
@ManyToOne(fetch = FetchType.EAGER)
45+
@ManyToOne(fetch = FetchType.LAZY, optional = true)
4346
@JoinColumn(name = "unit_id", nullable = false)
47+
@NotFound(action = NotFoundAction.IGNORE)
4448
private Unit unit;
4549

4650
@Column(name = "image_url")

backend/src/main/java/com/smalltrend/repository/ProductVariantRepository.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
import org.springframework.stereotype.Repository;
88

99
import java.util.List;
10+
import java.util.Map;
1011
import java.util.Optional;
1112

1213
@Repository
1314
public interface ProductVariantRepository extends JpaRepository<ProductVariant, Integer> {
15+
1416
Optional<ProductVariant> findBySku(String sku);
1517

1618
Optional<ProductVariant> findByBarcode(String barcode);
@@ -36,9 +38,28 @@ public interface ProductVariantRepository extends JpaRepository<ProductVariant,
3638
long countByUnitId(Integer unitId);
3739

3840
// Tìm biến thể theo SKU hoặc tên sản phẩm (không phân biệt hoa thường).
39-
@Query("SELECT pv FROM ProductVariant pv " +
40-
"LEFT JOIN pv.product p " +
41-
"WHERE LOWER(pv.sku) LIKE LOWER(CONCAT('%', :keyword, '%')) " +
42-
"OR LOWER(p.name) LIKE LOWER(CONCAT('%', :keyword, '%'))")
41+
@Query("SELECT pv FROM ProductVariant pv "
42+
+ "LEFT JOIN pv.product p "
43+
+ "WHERE LOWER(pv.sku) LIKE LOWER(CONCAT('%', :keyword, '%')) "
44+
+ "OR LOWER(p.name) LIKE LOWER(CONCAT('%', :keyword, '%'))")
4345
List<ProductVariant> searchByKeyword(@Param("keyword") String keyword);
46+
47+
@Query(value = "SELECT pv.id AS id, "
48+
+ "COALESCE(p.name, '') AS productName, "
49+
+ "COALESCE(u.name, '') AS unitName, "
50+
+ "pv.sku AS sku, "
51+
+ "pv.barcode AS barcode, "
52+
+ "pv.plu_code AS pluCode, "
53+
+ "pv.sell_price AS sellPrice, "
54+
+ "pv.is_active AS isActive, "
55+
+ "pv.image_url AS imageUrl, "
56+
+ "COALESCE(p.is_active, 1) AS productActive, "
57+
+ "COALESCE(SUM(s.quantity), 0) AS stockQuantity "
58+
+ "FROM product_variants pv "
59+
+ "LEFT JOIN products p ON p.id = pv.product_id "
60+
+ "LEFT JOIN units u ON u.id = pv.unit_id "
61+
+ "LEFT JOIN inventory_stock s ON s.variant_id = pv.id "
62+
+ "GROUP BY pv.id, p.name, u.name, pv.sku, pv.barcode, pv.plu_code, pv.sell_price, pv.is_active, pv.image_url, p.is_active",
63+
nativeQuery = true)
64+
List<Map<String, Object>> findVariantSummariesNative();
4465
}

backend/src/main/java/com/smalltrend/service/products/ProductVariantService.java

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.springframework.stereotype.Service;
2121
import lombok.RequiredArgsConstructor;
2222

23+
import java.math.BigDecimal;
2324
import java.util.List;
2425
import java.util.Map;
2526
import java.util.Set;
@@ -71,9 +72,15 @@ public List<ProductVariantRespone> getAllProductVariants(String search, String b
7172
variants = productVariantRepository.findAll();
7273
}
7374

74-
return variants.stream()
75+
List<ProductVariantRespone> responses = variants.stream()
7576
.map(this::mapToResponse)
7677
.collect(Collectors.toList());
78+
79+
if (!responses.isEmpty()) {
80+
return responses;
81+
}
82+
83+
return fallbackVariantSummaries(search, barcode);
7784
}
7885

7986
public List<ProductVariantRespone> getVariantsByProductId(Integer productId) {
@@ -577,4 +584,111 @@ private UnitConversionResponse mapConversionToResponse(UnitConversion entity) {
577584
response.setIsActive(entity.isActive());
578585
return response;
579586
}
587+
588+
private List<ProductVariantRespone> fallbackVariantSummaries(String search, String barcode) {
589+
String normalizedSearch = search != null ? search.trim().toLowerCase(Locale.ROOT) : null;
590+
String normalizedBarcode = barcode != null ? barcode.trim() : null;
591+
592+
return productVariantRepository.findVariantSummariesNative().stream()
593+
.map(this::mapNativeSummaryToResponse)
594+
.filter(item -> {
595+
if (normalizedBarcode != null && !normalizedBarcode.isEmpty()) {
596+
return item.getBarcode() != null && item.getBarcode().contains(normalizedBarcode);
597+
}
598+
if (normalizedSearch != null && !normalizedSearch.isEmpty()) {
599+
String name = item.getName() != null ? item.getName().toLowerCase(Locale.ROOT) : "";
600+
String sku = item.getSku() != null ? item.getSku().toLowerCase(Locale.ROOT) : "";
601+
String barcodeValue = item.getBarcode() != null ? item.getBarcode().toLowerCase(Locale.ROOT) : "";
602+
return name.contains(normalizedSearch)
603+
|| sku.contains(normalizedSearch)
604+
|| barcodeValue.contains(normalizedSearch);
605+
}
606+
return true;
607+
})
608+
.collect(Collectors.toList());
609+
}
610+
611+
private ProductVariantRespone mapNativeSummaryToResponse(Map<String, Object> row) {
612+
ProductVariantRespone response = new ProductVariantRespone();
613+
614+
Integer id = toInteger(row.get("id"));
615+
String productName = toStringValue(row.get("productName"));
616+
String unitName = toStringValue(row.get("unitName"));
617+
618+
response.setId(id);
619+
response.setSku(toStringValue(row.get("sku")));
620+
response.setBarcode(toStringValue(row.get("barcode")));
621+
response.setPluCode(toStringValue(row.get("pluCode")));
622+
response.setSellPrice(toBigDecimal(row.get("sellPrice")));
623+
response.setIsActive(toBoolean(row.get("isActive")));
624+
response.setProductActive(toBoolean(row.get("productActive")));
625+
response.setImageUrl(toStringValue(row.get("imageUrl")));
626+
response.setUnitName(unitName);
627+
response.setStockQuantity(Math.max(0, toInteger(row.get("stockQuantity"))));
628+
629+
String displayName = (productName + " " + unitName).trim();
630+
if (displayName.isEmpty()) {
631+
displayName = response.getSku() != null && !response.getSku().isBlank()
632+
? response.getSku()
633+
: "Sản phẩm";
634+
}
635+
response.setName(displayName);
636+
637+
return response;
638+
}
639+
640+
private Integer toInteger(Object value) {
641+
if (value == null) {
642+
return 0;
643+
}
644+
if (value instanceof Number number) {
645+
return number.intValue();
646+
}
647+
try {
648+
return Integer.parseInt(String.valueOf(value));
649+
} catch (NumberFormatException ex) {
650+
return 0;
651+
}
652+
}
653+
654+
private BigDecimal toBigDecimal(Object value) {
655+
if (value == null) {
656+
return null;
657+
}
658+
if (value instanceof BigDecimal decimal) {
659+
return decimal;
660+
}
661+
if (value instanceof Number number) {
662+
return BigDecimal.valueOf(number.doubleValue());
663+
}
664+
try {
665+
return new BigDecimal(String.valueOf(value));
666+
} catch (NumberFormatException ex) {
667+
return null;
668+
}
669+
}
670+
671+
private Boolean toBoolean(Object value) {
672+
if (value == null) {
673+
return null;
674+
}
675+
if (value instanceof Boolean bool) {
676+
return bool;
677+
}
678+
if (value instanceof Number number) {
679+
return number.intValue() != 0;
680+
}
681+
String text = String.valueOf(value).trim();
682+
if (text.isEmpty()) {
683+
return null;
684+
}
685+
return "1".equals(text)
686+
|| "true".equalsIgnoreCase(text)
687+
|| "t".equalsIgnoreCase(text)
688+
|| "yes".equalsIgnoreCase(text);
689+
}
690+
691+
private String toStringValue(Object value) {
692+
return value == null ? null : String.valueOf(value);
693+
}
580694
}

backend/src/main/java/com/smalltrend/service/ShiftWorkforceService.java renamed to backend/src/main/java/com/smalltrend/service/shift/ShiftWorkforceService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.smalltrend.service;
1+
package com.smalltrend.service.shift;
22

33
import com.smalltrend.dto.shift.AttendanceResponse;
44
import com.smalltrend.dto.shift.AttendanceUpsertRequest;

backend/src/main/java/com/smalltrend/service/WorkShiftAssignmentService.java renamed to backend/src/main/java/com/smalltrend/service/shift/WorkShiftAssignmentService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.smalltrend.service;
1+
package com.smalltrend.service.shift;
22

33
import com.smalltrend.dto.shift.ShiftAssignmentRequest;
44
import com.smalltrend.dto.shift.ShiftAssignmentResponse;

backend/src/main/java/com/smalltrend/service/WorkShiftService.java renamed to backend/src/main/java/com/smalltrend/service/shift/WorkShiftService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.smalltrend.service;
1+
package com.smalltrend.service.shift;
22

33
import com.smalltrend.dto.shift.WorkShiftRequest;
44
import com.smalltrend.dto.shift.WorkShiftResponse;

backend/src/test/java/com/smalltrend/controller/ShiftControllerTest.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.smalltrend.controller;
22

3+
import com.smalltrend.controller.shift.ShiftController;
34
import com.smalltrend.dto.common.MessageResponse;
45
import com.smalltrend.dto.shift.AttendanceResponse;
56
import com.smalltrend.dto.shift.AttendanceUpsertRequest;
@@ -9,9 +10,9 @@
910
import com.smalltrend.dto.shift.ShiftSwapExecuteRequest;
1011
import com.smalltrend.dto.shift.WorkShiftRequest;
1112
import com.smalltrend.dto.shift.WorkShiftResponse;
12-
import com.smalltrend.service.ShiftWorkforceService;
13-
import com.smalltrend.service.WorkShiftAssignmentService;
14-
import com.smalltrend.service.WorkShiftService;
13+
import com.smalltrend.service.shift.ShiftWorkforceService;
14+
import com.smalltrend.service.shift.WorkShiftAssignmentService;
15+
import com.smalltrend.service.shift.WorkShiftService;
1516
import com.smalltrend.validation.ShiftValidator;
1617
import org.junit.jupiter.api.BeforeEach;
1718
import org.junit.jupiter.api.Test;

backend/src/test/java/com/smalltrend/service/ShiftWorkforceServiceTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import com.smalltrend.repository.PayrollCalculationRepository;
1414
import com.smalltrend.repository.UserRepository;
1515
import com.smalltrend.repository.WorkShiftAssignmentRepository;
16+
import com.smalltrend.service.shift.ShiftWorkforceService;
1617
import org.junit.jupiter.api.BeforeEach;
1718
import org.junit.jupiter.api.Test;
1819
import org.junit.jupiter.api.extension.ExtendWith;

0 commit comments

Comments
 (0)