Untitled

 avatar
unknown
plain_text
a month ago
7.7 kB
4
Indexable
package replenishment_inbound_order

import (
	v2 "code.byted.org/oec/pbgen/go/oec/supply_chain/merchant/v2"
	"code.byted.org/oec/rpcv2_oec_supply_chain_business_order/kitex_gen/oec/supply_chain/business_order_base"
	"code.byted.org/oec/rpcv2_oec_supply_chain_planning_calc/kitex_gen/oec/supply_chain/planning_calc"
	"code.byted.org/oec/rpcv2_oec_supply_chain_warehouse_meta/kitex_gen/oec/supply_chain/warehouse_meta"
	"code.byted.org/oec/sc_merchant_gateway/consts"
	"code.byted.org/oec/sc_merchant_gateway/internal/cerr"
	"code.byted.org/oec/sc_merchant_gateway/util"
	"context"
	"sort"
	"time"
)

func (i *ReplenishmentInboundOrderLogicImpl) GetReplenishmentAvailableInboundMethod(ctx context.Context, req *v2.GetReplenishmentAvailableInboundMethodRequest) (*v2.GetReplenishmentAvailableInboundMethodResponse, error) {
	inboundOrderId := req.GetOrderId()
	inboundOrderList, err := i.BusinessOrderClient.GetReplenishmentInboundOrder(ctx, []int64{inboundOrderId})
	if err != nil {
		return nil, err
	}
	if len(inboundOrderList) == 0 {
		return nil, cerr.ErrNotExists{Key: "order"}
	}
	inboundOrder := inboundOrderList[0]

	ipeReq := planning_calc.GetInboundMethodRequest{
		MerchantId:   inboundOrder.MerchantId,
		EtaRangeList: buildTimeRangeForIpe(req.GetExpectedArrivalTimeRange().GetStart()),
		GoodsUnits:   int64(inboundOrder.GetExpectedCount()),
		CartonCnt:    int64(inboundOrder.GetCartonsQuantity()),
		NeedInboundMethodList: []planning_calc.InboundMethod{
			planning_calc.InboundMethod_D2FC,
			planning_calc.InboundMethod_SingleHub,
			planning_calc.InboundMethod_MultiHub,
		},
	}
	timeRangeMap := i.GatewayConfig.GetInboundMethodTimeRangeConfig(ctx)
	methodList, err := i.ReplPlanningCalcClient.GetInboundMethod(ctx, ipeReq)
	if err != nil {
		return nil, err
	}
	// get the warehouse data
	warehouseIdList := make([]int64, 0)
	sort.Slice(methodList.InboundMethodResultList, func(i, j int) bool {
		return methodList.InboundMethodResultList[i].EtaRange.EtaStartTime < methodList.InboundMethodResultList[i].EtaRange.EtaStartTime
	})
	//the first element we need to get the warehouse list
	for _, method := range methodList.InboundMethodResultList[0].InboundMethodDetailList {
		if len(method.WarehouseIds) > 0 {
			warehouseIdList = append(warehouseIdList, method.WarehouseIds...)
		}
	}
	//get the earliest available week by other elements
	nextAvailableWeekMap := buildNextAvailableWeek(methodList.GetInboundMethodResultList())
	request := &warehouse_meta.GetWarehouseByIdsRequest{
		WarehouseIds: warehouseIdList,
	}
	warehouseList, err := i.WarehouseClient.GetWarehouseMetaByIds(ctx, request)
	if err != nil {
		return nil, err
	}
	warehouseInfoMap := make(map[int64]*v2.WarehouseSimpleInfo)
	for _, warehouseDetail := range warehouseList {
		warehouseInfoMap[warehouseDetail.WarehouseBasicInfo.WarehouseId] = util.ConvertWarehouseInfo2WarehouseSimpleInfo(warehouseDetail)
	}
	availableMethodList := make([]*v2.AvailableInboundMethod, 0)
	for _, method := range methodList.InboundMethodResultList[0].InboundMethodDetailList {
		inboundMethod := v2.InboundMethod(method.InboundMethod)
		earliestAppointmentDate, err := i.getAvailableAppointment(ctx, *method, inboundOrder)
		if err != nil {
			return nil, err
		}
		availableWarehouseList := make([]*v2.WarehouseSimpleInfo, 0)
		for _, id := range method.GetWarehouseIds() {
			availableWarehouseList = append(availableWarehouseList, warehouseInfoMap[id])
		}
		availableMethod := &v2.AvailableInboundMethod{
			InboundMethod: inboundMethod,
			Sla: &v2.TimeRange{
				Start: timeRangeMap[inboundMethod].Min,
				End:   timeRangeMap[inboundMethod].Max,
			},
			ValidMethod:             true,
			EarliestAppointmentDate: earliestAppointmentDate,
			OptionalWarehouseList:   availableWarehouseList,
			NextAvailableWeek:       nextAvailableWeekMap[inboundMethod],
		}
		if len(availableWarehouseList) == 0 {
			availableMethod.ValidMethod = false
		}
		availableMethodList = append(availableMethodList, availableMethod)
	}
	resp := &v2.GetReplenishmentAvailableInboundMethodResponse{
		AvailableInboundMethodList: availableMethodList,
	}
	if err != nil {
		return nil, err
	}
	return resp, nil
}

func (i *ReplenishmentInboundOrderLogicImpl) getAvailableAppointment(ctx context.Context, method planning_calc.InboundMethodDetail, inboundOrder *business_order_base.InboundOrder) (int64, error) {
	appointmentTimeList := make([][]int64, 0)
	for _, warehouseId := range method.WarehouseIds {
		tempList := make([]int64, 0)
		calendar, err := i.CapacityPlanClient.GetInboundCapacityCalendar(ctx, &v2.GetBookableTimeslotRequest{
			InboundOrderId: inboundOrder.GetOrderId(),
			WarehouseId:    warehouseId,
			ShippingMethod: v2.AppointmentShippingMethodType_APPOINTMENT_SHIPPING_METHOD_TYPE_PALLETIZED,
			TimeRange: &v2.TimeRange{
				Start: time.Now().UnixMilli(),
				End:   time.Now().UnixMilli() + consts.FOUR_WEEK_MILLI,
			},
			PalletCount: 1,
			CartonCount: inboundOrder.GetCartonsQuantity(),
		})
		if err != nil {
			return 0, nil
		}
		if calendar == nil || len(calendar.CapacityList) <= 0 ||
			len(calendar.CapacityList[0].DaysCapacity) <= 0 {
			return consts.EARLIEST_APPOINTMENT_DATE, nil
		}
		targetDayCapacity := calendar.CapacityList[0].DaysCapacity[0].WaveList
		for _, item := range targetDayCapacity {
			if item.AvailableCapacity > 0 {
				tempList = append(tempList, item.StartTime)
			}
		}
		if len(tempList) == 0 {
			return consts.EARLIEST_APPOINTMENT_DATE, cerr.ErrValidation{Message: "no appointment is available for next four weeks"}
		}
		appointmentTimeList = append(appointmentTimeList, tempList)
	}
	appointmentTime, err := findLatestAppointment(appointmentTimeList)
	if err != nil {
		return 0, err
	}
	return appointmentTime, nil
}

func findLatestAppointment(allTimes [][]int64) (int64, error) {
	var maxTime int64 = 0
	for _, times := range allTimes {
		for _, t := range times {
			if t > maxTime {
				maxTime = t
			}
		}
	}
	return daysUntil(maxTime), nil
}

func daysUntil(maxTime int64) int64 {
	latestDate := time.UnixMilli(maxTime).UTC()
	today := time.Now().UTC()
	todayDateOnly := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, time.UTC)
	latestDateOnly := time.Date(latestDate.Year(), latestDate.Month(), latestDate.Day(), 0, 0, 0, 0, time.UTC)
	days := latestDateOnly.Sub(todayDateOnly).Hours() / 24
	return int64(days)
}

func buildTimeRangeForIpe(start int64) []*planning_calc.EtaRange {
	var ranges []*planning_calc.EtaRange

	// Start time in seconds (assuming input is in milliseconds)
	startTime := time.Unix(start/1000, 0)

	// Adjust start time to the beginning of the next Sunday unless it's already a Sunday
	if startTime.Weekday() != time.Sunday {
		startTime = startTime.AddDate(0, 0, int(time.Sunday-startTime.Weekday()))
	}

	// Weekly duration in seconds
	oneWeek := int64(time.Hour * 24 * 7 / time.Second)

	// Generate weekly ranges for eight weeks
	for week := 0; week < 8; week++ {
		currentStart := startTime.Add(time.Duration(week) * time.Duration(oneWeek)).Unix()
		currentEnd := currentStart + oneWeek - 1

		ranges = append(ranges, &planning_calc.EtaRange{
			EtaStartTime: currentStart,
			EtaEndTime:   currentEnd,
		})
	}
	return ranges
}

func buildNextAvailableWeek(inboundMethodResult []*planning_calc.InboundMethodResult_) map[v2.InboundMethod]int64 {
	respMap := make(map[v2.InboundMethod]int64)
	for week, list := range inboundMethodResult {
		for _, result := range list.InboundMethodDetailList {
			if result.WarehouseIds != nil && len(result.GetWarehouseIds()) > 0 && respMap[v2.InboundMethod(result.GetInboundMethod())] != 0 {
				respMap[v2.InboundMethod(result.GetInboundMethod())] = int64(week)
			}
		}
	}
	return respMap
}
Leave a Comment