feat(appointments): unify pre-inbound planning and confirmed FBA bookings

This commit is contained in:
2026-10-10 10:38:06 +08:00
parent 24f5ccf7ff
commit c4c1bddeec
54 changed files with 1623 additions and 744 deletions
@@ -0,0 +1,14 @@
package com.baiyue.lastleg.inboundplan.mapper;
import org.apache.ibatis.annotations.*;
@Mapper
public interface FbaAppointmentCommandMapper {
record Command(String requestHash,String responseJson) { }
@Select("SELECT request_hash AS requestHash,response_json AS responseJson FROM ll_fba_appointment_command WHERE command_key=#{key}")
Command find(@Param("key") String key);
@Insert("INSERT INTO ll_fba_appointment_command(command_key,request_hash,operation,operator_id,created_at) VALUES(#{key},#{hash},#{operation},#{operator},#{createdAt})")
int begin(@Param("key") String key,@Param("hash") String hash,@Param("operation") String operation,@Param("operator") String operator,@Param("createdAt") java.time.LocalDateTime createdAt);
@Update("UPDATE ll_fba_appointment_command SET appointment_no=#{appointmentNo},response_json=#{response} WHERE command_key=#{key} AND response_json IS NULL")
int finish(@Param("key") String key,@Param("appointmentNo") String appointmentNo,@Param("response") String response);
}
@@ -2,6 +2,7 @@ package com.baiyue.lastleg.inboundplan.mapper;
import com.baiyue.lastleg.inboundplan.model.FbaAppointmentLineSnapshot;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
@@ -9,21 +10,33 @@ import java.util.List;
@Mapper
public interface InboundFbaAppointmentQueryMapper {
@Select("<script>SELECT id FROM ll_order WHERE id IN <foreach collection='ids' item='id' open='(' separator=',' close=')'>#{id}</foreach> ORDER BY id FOR UPDATE</script>")
@Options(flushCache = Options.FlushCachePolicy.TRUE, useCache = false)
List<Long> lockOrders(@Param("ids") List<Long> ids);
@Select("<script>SELECT COUNT(*) FROM ll_inbound_fba_appointment_item WHERE active=1 " +
"<if test='excludedId != null'>AND appointment_id&lt;&gt;#{excludedId} </if>AND (source_key IN " +
"<foreach collection='keys' item='key' open='(' separator=',' close=')'>#{key}</foreach> " +
"<if test='orderIds != null and !orderIds.isEmpty()'>OR order_id IN <foreach collection='orderIds' item='id' open='(' separator=',' close=')'>#{id}</foreach></if>)</script>")
long countActiveSources(@Param("keys") List<String> keys,@Param("orderIds") List<Long> orderIds,@Param("excludedId") Long excludedId);
@Select("<script>SELECT COUNT(*) FROM ll_order o "+com.baiyue.lastleg.appointmentplanning.AppointmentCoverageMapper.TRANSFER_JOIN+
" WHERE o.id IN <foreach collection='ids' item='id' open='(' separator=',' close=')'>#{id}</foreach> AND ("+
com.baiyue.lastleg.appointmentplanning.AppointmentCoverageMapper.SHIPMENT_MISSING+") </script>")
long countMissingShipmentReferences(@Param("ids") List<Long> ids);
@Select("<script>" +
"SELECT l.id AS lineId,l.plan_id AS planId,l.order_id AS orderId,l.inbound_code AS inboundCode," +
"l.fba_warehouse_code AS fbaWarehouseCode,p.customer_id AS customerId " +
"FROM ll_inbound_plan_line l JOIN ll_inbound_plan p ON p.id=l.plan_id " +
"WHERE l.id IN " +
"<foreach collection='lineIds' item='id' open='(' separator=',' close=')'>#{id}</foreach> " +
"ORDER BY l.id FOR UPDATE" +
"ORDER BY l.id" +
"</script>")
List<FbaAppointmentLineSnapshot> lockLines(@Param("lineIds") List<Long> lineIds);
List<FbaAppointmentLineSnapshot> selectedLines(@Param("lineIds") List<Long> lineIds);
@Select("<script>SELECT COUNT(*) FROM ll_inbound_fba_appointment_item i " +
"JOIN ll_inbound_fba_appointment a ON a.id=i.appointment_id " +
"WHERE a.appointment_status NOT IN ('CANCELLED','FAILED') " +
"<if test='excludedId != null'>AND a.id&lt;&gt;#{excludedId} </if>AND i.line_id IN " +
"<foreach collection='lineIds' item='id' open='(' separator=',' close=')'>#{id}</foreach></script>")
@Select("<script>SELECT COUNT(*) FROM ll_inbound_fba_appointment_item i WHERE i.active=1 " +
"<if test='excludedId != null'>AND i.appointment_id&lt;&gt;#{excludedId} </if>AND i.order_id IN " +
"(SELECT l.order_id FROM ll_inbound_plan_line l WHERE l.id IN <foreach collection='lineIds' item='id' open='(' separator=',' close=')'>#{id}</foreach>)</script>")
long countActiveAppointments(@Param("lineIds") List<Long> lineIds, @Param("excludedId") Long excludedId);
@Select("""
@@ -24,9 +24,9 @@ public interface InboundPlanMapper extends BaseMapper<InboundPlan> {
"<if test='q.fbaWarehouseCode != null and q.fbaWarehouseCode != \"\"'> AND l.fba_warehouse_code=#{q.fbaWarehouseCode}</if>" +
"<if test='q.appointmentStatus != null and q.appointmentStatus != \"\" and q.appointmentStatus != \"NOT_APPOINTED\"'> " +
"AND EXISTS (SELECT 1 FROM ll_inbound_fba_appointment_item ai JOIN ll_inbound_fba_appointment a ON a.id=ai.appointment_id " +
"WHERE ai.plan_id=p.id AND ai.customer_id=#{customerId} AND a.appointment_status=#{q.appointmentStatus})</if>" +
"WHERE ai.order_id=p.source_order_id AND ai.customer_id=#{customerId} AND a.appointment_status=#{q.appointmentStatus})</if>" +
"<if test='q.appointmentStatus == \"NOT_APPOINTED\"'> AND NOT EXISTS (SELECT 1 FROM ll_inbound_fba_appointment_item ai " +
"WHERE ai.plan_id=p.id)</if>" +
"WHERE ai.order_id=p.source_order_id AND ai.customer_id=p.customer_id AND ai.active=1)</if>" +
"<if test='q.createdFrom != null'> AND p.created_at &gt;=#{q.createdFrom}</if>" +
"<if test='q.createdTo != null'> AND p.created_at &lt;=#{q.createdTo}</if>";
@@ -35,7 +35,7 @@ public interface InboundPlanMapper extends BaseMapper<InboundPlan> {
"l.destination_warehouse_id,l.destination_warehouse_code,l.destination_warehouse_name,l.fba_warehouse_code," +
"COALESCE(l.required_package_count,o.package_count,0) package_count,p.total_volume_cbm,p.pallet_count,p.status," +
"COALESCE((SELECT a.appointment_status FROM ll_inbound_fba_appointment_item ai " +
"JOIN ll_inbound_fba_appointment a ON a.id=ai.appointment_id WHERE ai.plan_id=p.id " +
"JOIN ll_inbound_fba_appointment a ON a.id=ai.appointment_id WHERE ai.order_id=p.source_order_id AND ai.customer_id=p.customer_id " +
"ORDER BY a.created_at DESC,a.id DESC LIMIT 1),'NOT_APPOINTED') appointment_status,p.created_at,p.updated_at" +
PAGE_FROM + " ORDER BY p.created_at DESC,p.id DESC LIMIT #{limit} OFFSET #{offset}</script>")
List<InboundPlanSummary> pageRows(@Param("customerId") String customerId,
@@ -20,10 +20,10 @@ public interface InboundPlanQueryMapper {
" JOIN ll_inbound_plan p ON p.id=l.plan_id" +
" JOIN ll_order o ON o.id=l.order_id AND o.customer_id=p.customer_id" +
"<if test='customerId != null and customerId != \"\"'> AND p.customer_id=#{customerId} AND o.customer_id=#{customerId}</if>" +
" LEFT JOIN ll_inbound_fba_appointment_item ai ON ai.line_id=l.id" +
" LEFT JOIN ll_inbound_fba_appointment_item ai ON ai.order_id=l.order_id AND ai.customer_id=p.customer_id" +
" AND ai.id=(SELECT ai2.id FROM ll_inbound_fba_appointment_item ai2" +
" JOIN ll_inbound_fba_appointment a2 ON a2.id=ai2.appointment_id" +
" WHERE ai2.line_id=l.id AND ai2.customer_id=p.customer_id" +
" WHERE ai2.order_id=l.order_id AND ai2.customer_id=p.customer_id AND ai2.fba_warehouse_code=l.fba_warehouse_code" +
" ORDER BY a2.created_at DESC,ai2.id DESC LIMIT 1)" +
" LEFT JOIN ll_inbound_fba_appointment a ON a.id=ai.appointment_id" +
" WHERE 1=1" +
@@ -35,7 +35,7 @@ public interface InboundPlanQueryMapper {
"<if test='q.destinationWarehouseCode != null and q.destinationWarehouseCode != \"\"'> AND l.destination_warehouse_code=#{q.destinationWarehouseCode}</if>" +
"<if test='q.warehouse != null and q.warehouse != \"\"'> AND (l.destination_warehouse_id LIKE CONCAT('%',#{q.warehouse},'%') OR l.destination_warehouse_code LIKE CONCAT('%',#{q.warehouse},'%') OR l.destination_warehouse_name LIKE CONCAT('%',#{q.warehouse},'%'))</if>" +
"<if test='q.fbaWarehouseCode != null and q.fbaWarehouseCode != \"\"'> AND l.fba_warehouse_code LIKE CONCAT('%',#{q.fbaWarehouseCode},'%')</if>" +
"<choose><when test='q.appointmentStatus == \"UNAPPOINTED\"'> AND ai.id IS NULL</when>" +
"<choose><when test='q.appointmentStatus == \"UNAPPOINTED\"'> AND (ai.id IS NULL OR ai.active=0)</when>" +
"<when test='q.appointmentStatus != null and q.appointmentStatus != \"\"'> AND a.appointment_status=#{q.appointmentStatus}</when></choose>" +
"<choose><when test='q.dispatchStatus == \"SCHEDULED\"'> AND " + SCHEDULED_DISPATCH + "</when>" +
"<when test='q.dispatchStatus == \"UNSCHEDULED\"'> AND NOT " + SCHEDULED_DISPATCH + "</when></choose>" +
@@ -57,7 +57,7 @@ public interface InboundPlanQueryMapper {
"l.planned_pallet_count palletCount,p.assigned_flag assigned,p.merge_required mergeAllowed," +
"p.urgent_flag urgent,p.status status,a.appointment_no appointmentNo," +
"a.appointment_status appointmentStatus,a.appointment_at appointmentAt," +
"a.expected_loading_at expectedLoadingAt," +
"NULL expectedLoadingAt," +
"CASE WHEN " + SCHEDULED_DISPATCH +
" THEN 'SCHEDULED' ELSE 'UNSCHEDULED' END dispatchStatus,p.created_at createdAt,p.version version" +
FROM_AND_FILTER + " ORDER BY p.created_at DESC,l.id DESC LIMIT #{limit} OFFSET #{offset}</script>")