feat(appointments): expose bounded terminal pickup calendar
This commit is contained in:
@@ -12,6 +12,11 @@ import java.util.List;
|
||||
@FeignClient(name = "warehouse-service", contextId = "firstlegWarehouseMasterDataClient",
|
||||
url = "${baiyue.clients.warehouse.url:}", path = "/internal/warehouses")
|
||||
public interface WarehouseMasterDataClient {
|
||||
@GetMapping("/order-catalog")
|
||||
ApiResponse<List<CatalogWarehouse>> catalog(@RequestHeader("X-Internal-Service-Token") String token);
|
||||
|
||||
record CatalogWarehouse(Long id, String warehouseCode, String warehouseName, String warehouseType, String countryCode) { }
|
||||
|
||||
@GetMapping("/{tenantId}/{warehouseId}/active")
|
||||
ApiResponse<Detail> active(@RequestHeader("X-Internal-Service-Token") String token,
|
||||
@PathVariable long tenantId,
|
||||
|
||||
+11
@@ -41,6 +41,17 @@ import java.util.List;
|
||||
@RequestMapping("/api/platform/v1/terminal-drayages")
|
||||
public class PlatformTerminalDrayageController {
|
||||
@Resource private TerminalDrayageMailService mails;
|
||||
@Resource private com.baiyue.firstleg.terminaldrayage.service.TerminalDrayageCalendarService calendar;
|
||||
|
||||
@GetMapping("/calendar")
|
||||
@PreAuthorize("hasAuthority('platform:appointment:list') and hasAuthority('platform:firstleg:terminal-drayage:list')")
|
||||
public ApiResponse<com.baiyue.firstleg.terminaldrayage.service.TerminalDrayageCalendarService.Calendar> calendar(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam @org.springframework.format.annotation.DateTimeFormat(iso=org.springframework.format.annotation.DateTimeFormat.ISO.DATE) java.time.LocalDate from,
|
||||
@RequestParam @org.springframework.format.annotation.DateTimeFormat(iso=org.springframework.format.annotation.DateTimeFormat.ISO.DATE) java.time.LocalDate to) {
|
||||
FirstlegScopeGuard.platform(user);
|
||||
return ApiResponse.success(calendar.calendar(from,to));
|
||||
}
|
||||
|
||||
@PostMapping("/{containerBookingId}/pickup-email")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:terminal-drayage:fleet:edit')")
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package com.baiyue.firstleg.terminaldrayage.dto;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/** Original pickup appointment wall time; the source does not establish a port time zone. */
|
||||
public record PickupCalendarEntry(Long containerBookingId, String bookingNo, String containerNo,
|
||||
String destinationPortName, String destinationWarehouseCode, String fleetSupplierName,
|
||||
LocalDateTime pickupAppointmentAt, LocalDateTime actualPickupAt) { }
|
||||
+13
@@ -14,6 +14,19 @@ import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface TerminalDrayageMapper extends BaseMapper<TerminalDrayageEntity> {
|
||||
@Select("""
|
||||
SELECT c.id containerBookingId,c.booking_no bookingNo,c.container_no containerNo,
|
||||
c.destination_port_name destinationPortName,c.destination_warehouse_code destinationWarehouseCode,
|
||||
t.fleet_supplier_name fleetSupplierName,t.pickup_appointment_at pickupAppointmentAt,NULL actualPickupAt
|
||||
FROM fl_terminal_drayage t JOIN fl_container_booking c ON c.id=t.container_booking_id
|
||||
WHERE c.record_status='ACTIVE' AND t.pickup_appointment_at>=#{from} AND t.pickup_appointment_at<#{to}
|
||||
AND EXISTS(SELECT 1 FROM fl_outbound_dispatch d WHERE d.container_booking_id=c.id
|
||||
AND d.confirmed_at IS NOT NULL AND d.reversed_at IS NULL AND d.status='WAITING_FOR_LOADING')
|
||||
ORDER BY t.pickup_appointment_at,c.id LIMIT #{limit}
|
||||
""")
|
||||
List<com.baiyue.firstleg.terminaldrayage.dto.PickupCalendarEntry> calendar(
|
||||
@Param("from") LocalDateTime from,@Param("to") LocalDateTime to,@Param("limit") int limit);
|
||||
|
||||
@Select("""
|
||||
SELECT b.id AS containerBookingId,b.container_no AS containerNo,b.ocean_booking_id AS oceanBookingId,
|
||||
b.record_status AS recordStatus,b.unloading_completed_at AS unloadingCompletedAt
|
||||
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package com.baiyue.firstleg.terminaldrayage.service;
|
||||
|
||||
import com.baiyue.firstleg.client.WarehouseMasterDataClient;
|
||||
import com.baiyue.firstleg.terminaldrayage.dto.PickupCalendarEntry;
|
||||
import com.baiyue.firstleg.terminaldrayage.mapper.TerminalDrayageMapper;
|
||||
import com.baiyue.firstleg.portschedule.service.CarrierScheduleQueryService;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.*;
|
||||
|
||||
@Service
|
||||
public class TerminalDrayageCalendarService {
|
||||
private final TerminalDrayageMapper mapper;
|
||||
private final WarehouseMasterDataClient warehouses;
|
||||
private final String token;
|
||||
private final CarrierScheduleQueryService shipping;
|
||||
public TerminalDrayageCalendarService(TerminalDrayageMapper mapper,WarehouseMasterDataClient warehouses,
|
||||
CarrierScheduleQueryService shipping,
|
||||
@Value("${baiyue.security.internal-service-token}") String token) {this.mapper=mapper;this.warehouses=warehouses;this.shipping=shipping;this.token=token;}
|
||||
public record Calendar(List<PickupCalendarEntry> records,boolean truncated,int unclassifiedCount,
|
||||
String scopeBasis,String timeBasis) { }
|
||||
public Calendar calendar(LocalDate from,LocalDate to) {
|
||||
if(from==null || to==null || !to.isAfter(from) || ChronoUnit.DAYS.between(from,to)>42)
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST,"日历查询范围必须为1至42天");
|
||||
var rows=mapper.calendar(from.atStartOfDay(),to.atStartOfDay(),1001);
|
||||
if(rows.isEmpty()) return result(List.of(),false,0);
|
||||
final Map<String,String> countries=new HashMap<>();
|
||||
try {
|
||||
var response=warehouses.catalog(token);
|
||||
if(response==null || !response.successful() || response.data()==null) throw new IllegalStateException();
|
||||
for(var warehouse:response.data()) if(warehouse!=null && warehouse.warehouseCode()!=null && warehouse.countryCode()!=null)
|
||||
countries.put(warehouse.warehouseCode(),warehouse.countryCode());
|
||||
} catch(RuntimeException error) {throw new ResponseStatusException(HttpStatus.SERVICE_UNAVAILABLE,"提柜目的仓范围暂不可核实",error);}
|
||||
var records=new ArrayList<PickupCalendarEntry>();int unknown=0;
|
||||
for(var row:rows.stream().limit(1000).toList()) {
|
||||
var country=countries.get(row.destinationWarehouseCode());
|
||||
if(country==null || country.isBlank()) unknown++;
|
||||
else if("US".equals(country)) records.add(row);
|
||||
}
|
||||
var actual=new HashMap<Long,java.time.LocalDateTime>();
|
||||
var ids=records.stream().map(PickupCalendarEntry::containerBookingId).distinct().toList();
|
||||
for(int start=0;start<ids.size();start+=200) {
|
||||
for(var fact:shipping.containerActualFacts(ids.subList(start,Math.min(start+200,ids.size()))))
|
||||
if(fact.pickedUpAt()!=null) actual.put(fact.containerBookingId(),fact.pickedUpAt());
|
||||
}
|
||||
var enriched=records.stream().map(row->new PickupCalendarEntry(row.containerBookingId(),row.bookingNo(),row.containerNo(),
|
||||
row.destinationPortName(),row.destinationWarehouseCode(),row.fleetSupplierName(),row.pickupAppointmentAt(),actual.get(row.containerBookingId()))).toList();
|
||||
return result(enriched,rows.size()>1000,unknown);
|
||||
}
|
||||
private Calendar result(List<PickupCalendarEntry> records,boolean truncated,int unknown) {
|
||||
return new Calendar(List.copyOf(records),truncated,unknown,"US_DESTINATION_WAREHOUSE","SOURCE_LOCAL_UNSPECIFIED");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user