package de.votian.services.service;

import de.votian.services.config.ParameterKeys;
import de.votian.services.dto.JobPricingResultDto;
import de.votian.services.dto.TourDetailDto;
import de.votian.services.entity.CostCenter;
import de.votian.services.entity.Customer;
import de.votian.services.entity.Job;
import de.votian.services.entity.JobPrice;
import de.votian.services.entity.MetaType;
import de.votian.services.entity.Parameter;
import de.votian.services.entity.GenericDataContainer;
import de.votian.services.entity.ServiceEntity;
import de.votian.services.entity.ServiceHistory;
import de.votian.services.entity.ServicePriceHistory;
import de.votian.services.entity.ServiceType;
import de.votian.services.entity.ServiceZipCode;
import de.votian.services.entity.ServiceZipCodeArea;
import de.votian.services.entity.ServiceZipCodeAreaAddress;
import de.votian.services.entity.ServiceZipCodeAreaHistory;
import de.votian.services.entity.ServiceZipCodeHistory;
import de.votian.services.repository.CostCenterRepository;
import de.votian.services.repository.CustomerRepository;
import de.votian.services.repository.EmployeeRepository;
import de.votian.services.repository.GenericDataContainerRepository;
import de.votian.services.repository.HeadquartersRepository;
import de.votian.services.repository.MetaTypeRepository;
import de.votian.services.repository.ParameterRepository;
import de.votian.services.repository.ServiceEntityRepository;
import de.votian.services.repository.ServiceHistoryRepository;
import de.votian.services.repository.ServicePriceHistoryRepository;
import de.votian.services.repository.ServiceTypeRepository;
import de.votian.services.repository.ServiceZipCodeAreaAddressRepository;
import de.votian.services.repository.ServiceZipCodeAreaHistoryRepository;
import de.votian.services.repository.ServiceZipCodeAreaMappingRepository;
import de.votian.services.repository.ServiceZipCodeAreaRepository;
import de.votian.services.repository.ServiceZipCodeHistoryRepository;
import de.votian.services.repository.ServiceZipCodeRepository;
import de.votian.services.repository.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class JobPricingServiceTest {

    private static final LocalDateTime ORDER_TIME = LocalDateTime.of(2026, 3, 27, 10, 30);

    private CostCenterRepository costCenterRepository;
    private CustomerRepository customerRepository;
    private HeadquartersRepository headquartersRepository;
    private MetaTypeRepository metaTypeRepository;
    private ServiceEntityRepository serviceEntityRepository;
    private ServiceTypeRepository serviceTypeRepository;
    private ServiceHistoryRepository serviceHistoryRepository;
    private ServicePriceHistoryRepository servicePriceHistoryRepository;
    private ServiceZipCodeRepository serviceZipCodeRepository;
    private ServiceZipCodeHistoryRepository serviceZipCodeHistoryRepository;
    private ServiceZipCodeAreaRepository serviceZipCodeAreaRepository;
    private ServiceZipCodeAreaAddressRepository serviceZipCodeAreaAddressRepository;
    private ServiceZipCodeAreaHistoryRepository serviceZipCodeAreaHistoryRepository;
    private ServiceZipCodeAreaMappingRepository serviceZipCodeAreaMappingRepository;
    private GenericDataContainerRepository genericDataContainerRepository;
    private ParameterRepository parameterRepository;
    private EmployeeRepository employeeRepository;
    private UserRepository userRepository;
    private ParameterService parameterService;
    private JobPricingService jobPricingService;

    @BeforeEach
    void setUp() {
        costCenterRepository = mock(CostCenterRepository.class);
        customerRepository = mock(CustomerRepository.class);
        headquartersRepository = mock(HeadquartersRepository.class);
        metaTypeRepository = mock(MetaTypeRepository.class);
        serviceEntityRepository = mock(ServiceEntityRepository.class);
        serviceTypeRepository = mock(ServiceTypeRepository.class);
        serviceHistoryRepository = mock(ServiceHistoryRepository.class);
        servicePriceHistoryRepository = mock(ServicePriceHistoryRepository.class);
        serviceZipCodeRepository = mock(ServiceZipCodeRepository.class);
        serviceZipCodeHistoryRepository = mock(ServiceZipCodeHistoryRepository.class);
        serviceZipCodeAreaRepository = mock(ServiceZipCodeAreaRepository.class);
        serviceZipCodeAreaAddressRepository = mock(ServiceZipCodeAreaAddressRepository.class);
        serviceZipCodeAreaHistoryRepository = mock(ServiceZipCodeAreaHistoryRepository.class);
        serviceZipCodeAreaMappingRepository = mock(ServiceZipCodeAreaMappingRepository.class);
        genericDataContainerRepository = mock(GenericDataContainerRepository.class);
        parameterRepository = mock(ParameterRepository.class);
        employeeRepository = mock(EmployeeRepository.class);
        userRepository = mock(UserRepository.class);
        when(parameterRepository.findByKeyAndHqAndEmployee(anyString(), anyLong(), anyLong())).thenReturn(Optional.empty());
        when(genericDataContainerRepository.findByTypeAndIdAndField(anyString(), anyLong(), anyString())).thenReturn(Optional.empty());
        when(serviceHistoryRepository.findLatestPriceForType(anyLong(), anyLong(), anyLong(), anyLong(), any(LocalDateTime.class))).thenReturn(List.of());
        when(servicePriceHistoryRepository.findLatestFieldValue(anyString(), anyLong(), anyString(), any(LocalDateTime.class))).thenReturn(List.of());
        when(servicePriceHistoryRepository.findLatestFieldValueWithContext(anyString(), anyLong(), anyString(), anyString(), any(LocalDateTime.class))).thenReturn(List.of());
        when(serviceZipCodeHistoryRepository.findLatest(anyLong(), anyLong(), anyLong(), anyLong(), anyInt(), any(LocalDateTime.class))).thenReturn(List.of());
        when(serviceZipCodeAreaHistoryRepository.findLatest(anyLong(), anyLong(), anyLong(), anyLong(), anyInt(), any(LocalDateTime.class))).thenReturn(List.of());
        when(serviceZipCodeAreaAddressRepository.findAddressCandidates(anyLong(), anyString(), anyString())).thenReturn(List.of());
        parameterService = new ParameterService(parameterRepository, employeeRepository, userRepository);
        jobPricingService = new JobPricingService(
                costCenterRepository,
                customerRepository,
                headquartersRepository,
                metaTypeRepository,
                serviceEntityRepository,
                serviceTypeRepository,
                serviceHistoryRepository,
                servicePriceHistoryRepository,
                serviceZipCodeRepository,
                serviceZipCodeHistoryRepository,
                serviceZipCodeAreaRepository,
                serviceZipCodeAreaAddressRepository,
                serviceZipCodeAreaHistoryRepository,
                serviceZipCodeAreaMappingRepository,
                genericDataContainerRepository,
                parameterService
        );
    }

    @Test
    void calculateDraftAppliesFixpriceDiscountMarkupAndSelfService() {
        Job job = new Job();
        job.setHeadquartersId(1L);
        job.setCostCenterPayerId(10L);
        job.setFixPrice(new BigDecimal("100"));
        job.setServicePrice(new BigDecimal("20"));
        job.setMarkup(new BigDecimal("10"));
        job.setCourierSubprice(new BigDecimal("50"));

        JobPrice selfService = price(JobPricingService.JB_SELF_SERVICE_DISCOUNT, "10");
        JobPrice courierMarkup = price(JobPricingService.JB_CR_MARKUP_MAN, "5");

        stubCustomer(new BigDecimal("10"), new BigDecimal("20"), 0);
        stubParameter(ParameterKeys.INV_SERVICEPRICE_DISCOUNT, 1L, "1");
        stubParameter(ParameterKeys.MASK_CS_SELF_SERVICE_DISCOUNT, 1L, "1");

        JobPricingResultDto result = jobPricingService.calculateDraft(job, List.of(selfService, courierMarkup));

        assertThat(result.getJob().getSubtotalPrice()).isEqualByComparingTo("88.00");
        assertThat(result.getJob().getTotalPrice()).isEqualByComparingTo("95.20");
        assertThat(result.getJob().getCourierSubprice()).isEqualByComparingTo("36.00");
        assertThat(result.getJob().getCourierPrice()).isEqualByComparingTo("37.80");
        assertThat(findPrice(result, JobPricingService.JB_MARKUP_MAN)).isEqualByComparingTo("10.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_SUBTOTAL)).isEqualByComparingTo("36.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_PROV)).isEqualByComparingTo("59.09");
        assertThat(findPrice(result, JobPricingService.JB_SELF_SERVICE_DISCOUNT)).isEqualByComparingTo("10.00");
    }

    @Test
    void calculateDraftDerivesCourierServiceFromProvisionRate() {
        Job job = new Job();
        job.setHeadquartersId(1L);
        job.setCostCenterPayerId(10L);
        job.setFixPrice(new BigDecimal("100"));
        job.setServicePrice(new BigDecimal("10"));
        job.setMarkup(BigDecimal.ZERO);

        JobPrice courierMarkup = price(JobPricingService.JB_CR_MARKUP, "10");
        JobPrice courierProvision = price(JobPricingService.JB_CR_PROV, "20");

        stubCustomer(BigDecimal.ZERO, BigDecimal.ZERO, 0);
        stubParameter(ParameterKeys.INV_SERVICEPRICE_DISCOUNT, 1L, "1");
        stubParameter(ParameterKeys.MASK_CS_SELF_SERVICE_DISCOUNT, 1L, "0");

        JobPricingResultDto result = jobPricingService.calculateDraft(job, List.of(courierMarkup, courierProvision));

        assertThat(result.getJob().getSubtotalPrice()).isEqualByComparingTo("110.00");
        assertThat(result.getJob().getTotalPrice()).isEqualByComparingTo("110.00");
        assertThat(result.getJob().getCourierSubprice()).isEqualByComparingTo("80.00");
        assertThat(result.getJob().getCourierPrice()).isEqualByComparingTo("96.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_SERVICEPRICE)).isEqualByComparingTo("8.00");
    }

    @Test
    void calculateDraftAddsAutomaticStreetServicePriceOnlyToCustomerSideForNewJobs() {
        Job job = new Job();
        job.setHeadquartersId(1L);
        job.setCostCenterPayerId(10L);
        job.setOrderTime(ORDER_TIME);
        job.setFixPrice(new BigDecimal("100"));
        job.setServicePrice(new BigDecimal("20"));

        stubCustomer(new BigDecimal("10"), BigDecimal.ZERO, 0);
        stubParameter(ParameterKeys.INV_SERVICEPRICE_DISCOUNT, 1L, "1");
        stubParameter(ParameterKeys.MASK_SERVICE_PRICE_STREET, 1L, "20095,Hauptstrasse,1-9,2-10,2026-01-01,2026-12-31,5");

        TourDetailDto pickup = new TourDetailDto();
        pickup.setSort(1);
        pickup.setZipcode("20095");
        pickup.setStreet("Hauptstrasse West");
        pickup.setHsno("4a");

        JobPricingResultDto result = jobPricingService.calculateDraft(
                job,
                List.of(price(JobPricingService.JB_CR_PROV, "20")),
                List.of(pickup)
        );

        assertThat(result.getJob().getServicePrice()).isEqualByComparingTo("25.00");
        assertThat(result.getJob().getSubtotalPrice()).isEqualByComparingTo("113.00");
        assertThat(result.getJob().getTotalPrice()).isEqualByComparingTo("113.00");
        assertThat(result.getJob().getCourierSubprice()).isEqualByComparingTo("64.80");
        assertThat(result.getJob().getCourierPrice()).isEqualByComparingTo("79.20");
        assertThat(findPrice(result, JobPricingService.JB_CR_SERVICEPRICE)).isEqualByComparingTo("14.40");
        assertThat(result.getBaseServicePrice()).isEqualByComparingTo("20.00");
        assertThat(result.getAutomaticStreetServicePrice()).isEqualByComparingTo("5.00");
    }

    @Test
    void calculateDraftUsesStoredAutomaticStreetServicePriceForPersistedJobs() {
        Job job = new Job();
        job.setId(5L);
        job.setHeadquartersId(1L);
        job.setCostCenterPayerId(10L);
        job.setOrderTime(ORDER_TIME);
        job.setFixPrice(new BigDecimal("100"));
        job.setServicePrice(new BigDecimal("25"));

        stubCustomer(new BigDecimal("10"), BigDecimal.ZERO, 0);
        stubParameter(ParameterKeys.INV_SERVICEPRICE_DISCOUNT, 1L, "1");

        GenericDataContainer automaticStreetServicePrice = new GenericDataContainer();
        automaticStreetServicePrice.setObjectType("jb");
        automaticStreetServicePrice.setObjectId(5L);
        automaticStreetServicePrice.setFieldName("jb_serviceprice_street_auto");
        automaticStreetServicePrice.setContent("5.00");
        when(genericDataContainerRepository.findByTypeAndIdAndField("jb", 5L, "jb_serviceprice_street_auto"))
                .thenReturn(Optional.of(automaticStreetServicePrice));

        JobPricingResultDto result = jobPricingService.calculateDraft(
                job,
                List.of(price(JobPricingService.JB_CR_PROV, "20")),
                List.of()
        );

        assertThat(result.getJob().getServicePrice()).isEqualByComparingTo("25.00");
        assertThat(result.getJob().getSubtotalPrice()).isEqualByComparingTo("113.00");
        assertThat(result.getJob().getTotalPrice()).isEqualByComparingTo("113.00");
        assertThat(result.getJob().getCourierSubprice()).isEqualByComparingTo("64.80");
        assertThat(result.getJob().getCourierPrice()).isEqualByComparingTo("79.20");
        assertThat(findPrice(result, JobPricingService.JB_CR_SERVICEPRICE)).isEqualByComparingTo("14.40");
        assertThat(result.getBaseServicePrice()).isEqualByComparingTo("20.00");
        assertThat(result.getAutomaticStreetServicePrice()).isEqualByComparingTo("5.00");
    }

    @Test
    void calculateDraftUsesLegacyCourierServicePriceMetaSortThree() {
        Job job = new Job();
        job.setHeadquartersId(1L);
        job.setCostCenterPayerId(10L);
        job.setOrderTime(ORDER_TIME);
        job.setFixPrice(new BigDecimal("100"));
        job.setServicePrice(new BigDecimal("10"));
        job.setCourierPrice(new BigDecimal("87"));

        stubCustomer(BigDecimal.ZERO, BigDecimal.ZERO, 0);
        stubParameter(ParameterKeys.INV_SERVICEPRICE_DISCOUNT, 1L, "0");
        stubParameter(ParameterKeys.MASK_CR_PRICE_MODE, 1L, "1");
        stubParameter(ParameterKeys.MASK_CR_PRICE_MODE_DATE, 1L, "2023-04-01");

        JobPricingResultDto result = jobPricingService.calculateDraft(
                job,
                List.of(price(JobPricingService.JB_CR_FIXSERVICEPRICE, "7"))
        );

        assertThat(result.getJob().getCourierSubprice()).isEqualByComparingTo("80.00");
        assertThat(result.getJob().getCourierPrice()).isEqualByComparingTo("87.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_FIXSERVICEPRICE)).isEqualByComparingTo("7.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_SUBTOTAL)).isEqualByComparingTo("87.00");
        assertThat(hasPrice(result, JobPricingService.JB_CR_SERVICEPRICE)).isFalse();
    }

    @Test
    void calculateDraftUsesLegacyCourierFixpriceMetaSortTwoForFixedPriceJobs() {
        Job job = new Job();
        job.setHeadquartersId(1L);
        job.setCostCenterPayerId(10L);
        job.setFixPrice(new BigDecimal("100"));

        stubCustomer(BigDecimal.ZERO, new BigDecimal("20"), 0);

        JobPricingResultDto result = jobPricingService.calculateDraft(
                job,
                List.of(
                        price(JobPricingService.JB_CR_FIXPRICE, "60"),
                        price(JobPricingService.JB_CR_MARKUP, "5"),
                        price(JobPricingService.JB_SELF_SERVICE_DISCOUNT, "10")
                )
        );

        assertThat(result.getJob().getSubtotalPrice()).isEqualByComparingTo("72.00");
        assertThat(result.getJob().getTotalPrice()).isEqualByComparingTo("72.00");
        assertThat(result.getJob().getCourierSubprice()).isEqualByComparingTo("54.00");
        assertThat(result.getJob().getCourierPrice()).isEqualByComparingTo("56.70");
        assertThat(findPrice(result, JobPricingService.JB_CR_FIXPRICE)).isEqualByComparingTo("60.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_SUBTOTAL)).isEqualByComparingTo("54.00");
        assertThat(hasPrice(result, JobPricingService.JB_CR_PROV)).isFalse();
    }

    @Test
    void calculateDraftUsesLegacyCourierFixpriceMetaSortTwoAsRouteOverride() {
        Job job = new Job();
        job.setHeadquartersId(1L);
        job.setCostCenterPayerId(10L);
        job.setOrderTime(ORDER_TIME);
        job.setVehicleTypeId(2);

        stubCustomer(BigDecimal.ZERO, BigDecimal.ZERO, 1);

        MetaType vehicleType = new MetaType();
        vehicleType.setSort(2);
        vehicleType.setType("vehicletype");
        vehicleType.setValue("PKW");
        when(metaTypeRepository.findBySortAndType(2, "vehicletype")).thenReturn(Optional.of(vehicleType));

        ServiceZipCode fromZipCode = new ServiceZipCode();
        fromZipCode.setId(1L);
        fromZipCode.setZipcode("20095");
        ServiceZipCode toZipCode = new ServiceZipCode();
        toZipCode.setId(2L);
        toZipCode.setZipcode("10115");
        when(serviceZipCodeRepository.findByZipcode("20095")).thenReturn(Optional.of(fromZipCode));
        when(serviceZipCodeRepository.findByZipcode("10115")).thenReturn(Optional.of(toZipCode));

        ServiceZipCodeHistory routeHistory = new ServiceZipCodeHistory();
        routeHistory.setFromZipCodeId(1L);
        routeHistory.setToZipCodeId(2L);
        routeHistory.setHeadquartersId(1L);
        routeHistory.setCustomerId(0L);
        routeHistory.setVehicleTypeId(2);
        routeHistory.setValidFrom(ORDER_TIME.minusDays(1));
        routeHistory.setPrice(new BigDecimal("100"));

        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 100L, 1L, 2, ORDER_TIME)).thenReturn(List.of());
        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 0L, 1L, 2, ORDER_TIME)).thenReturn(List.of(routeHistory));

        TourDetailDto pickup = new TourDetailDto();
        pickup.setSort(1);
        pickup.setZipcode("20095");
        TourDetailDto delivery = new TourDetailDto();
        delivery.setSort(2);
        delivery.setZipcode("10115");

        JobPricingResultDto result = jobPricingService.calculateDraft(
                job,
                List.of(
                        price(JobPricingService.JB_CR_FIXPRICE, "60"),
                        price(JobPricingService.JB_CR_MARKUP, "5"),
                        price(JobPricingService.JB_SELF_SERVICE_DISCOUNT, "10")
                ),
                List.of(pickup, delivery)
        );

        assertThat(result.getJob().getSubtotalPrice()).isEqualByComparingTo("100.00");
        assertThat(result.getJob().getTotalPrice()).isEqualByComparingTo("100.00");
        assertThat(result.getJob().getCourierSubprice()).isEqualByComparingTo("54.00");
        assertThat(result.getJob().getCourierPrice()).isEqualByComparingTo("56.70");
        assertThat(findPrice(result, JobPricingService.JB_CR_FIXPRICE)).isEqualByComparingTo("60.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_SUBTOTAL)).isEqualByComparingTo("54.00");
        assertThat(hasPrice(result, JobPricingService.JB_CR_PROV)).isFalse();
    }

    @Test
    void calculateDraftUsesManualCourierTotalOverrideBeforeCrPriceModeDate() {
        Job job = new Job();
        job.setHeadquartersId(1L);
        job.setCostCenterPayerId(10L);
        job.setOrderTime(ORDER_TIME);
        job.setFixPrice(new BigDecimal("100"));
        job.setServicePrice(new BigDecimal("10"));
        job.setCourierPrice(new BigDecimal("63.00"));

        stubCustomer(BigDecimal.ZERO, new BigDecimal("20"), 0);
        stubParameter(ParameterKeys.INV_SERVICEPRICE_DISCOUNT, 1L, "0");
        stubParameter(ParameterKeys.MASK_CR_PRICE_MODE, 1L, "1");
        stubParameter(ParameterKeys.MASK_CR_PRICE_MODE_DATE, 1L, "2026-04-01");

        JobPricingResultDto result = jobPricingService.calculateDraft(
                job,
                List.of(
                        price(JobPricingService.JB_CR_MARKUP, "5"),
                        price(JobPricingService.JB_CR_FIXSERVICEPRICE, "7.50")
                ),
                List.of(),
                true
        );

        assertThat(result.getJob().getSubtotalPrice()).isEqualByComparingTo("90.00");
        assertThat(result.getJob().getTotalPrice()).isEqualByComparingTo("90.00");
        assertThat(result.getJob().getCourierSubprice()).isEqualByComparingTo("60.00");
        assertThat(result.getJob().getCourierPrice()).isEqualByComparingTo("63.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_SUBTOTAL)).isEqualByComparingTo("60.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_FIXSERVICEPRICE)).isEqualByComparingTo("7.50");
        assertThat(hasPrice(result, JobPricingService.JB_CR_PROV)).isFalse();
    }

    @Test
    void calculateDraftUsesManualCourierTotalOverrideAfterCrPriceModeDateForRouteJobs() {
        Job job = new Job();
        job.setHeadquartersId(1L);
        job.setCostCenterPayerId(10L);
        job.setOrderTime(ORDER_TIME);
        job.setVehicleTypeId(2);
        job.setServicePrice(new BigDecimal("10"));
        job.setCourierPrice(new BigDecimal("64.20"));

        stubCustomer(BigDecimal.ZERO, BigDecimal.ZERO, 1);
        stubParameter(ParameterKeys.INV_SERVICEPRICE_DISCOUNT, 1L, "0");
        stubParameter(ParameterKeys.MASK_CR_PRICE_MODE, 1L, "1");
        stubParameter(ParameterKeys.MASK_CR_PRICE_MODE_DATE, 1L, "2023-04-01");

        MetaType vehicleType = new MetaType();
        vehicleType.setSort(2);
        vehicleType.setType("vehicletype");
        vehicleType.setValue("PKW");
        when(metaTypeRepository.findBySortAndType(2, "vehicletype")).thenReturn(Optional.of(vehicleType));

        ServiceZipCode fromZipCode = new ServiceZipCode();
        fromZipCode.setId(1L);
        fromZipCode.setZipcode("20095");
        ServiceZipCode toZipCode = new ServiceZipCode();
        toZipCode.setId(2L);
        toZipCode.setZipcode("10115");
        when(serviceZipCodeRepository.findByZipcode("20095")).thenReturn(Optional.of(fromZipCode));
        when(serviceZipCodeRepository.findByZipcode("10115")).thenReturn(Optional.of(toZipCode));

        ServiceZipCodeHistory routeHistory = new ServiceZipCodeHistory();
        routeHistory.setFromZipCodeId(1L);
        routeHistory.setToZipCodeId(2L);
        routeHistory.setHeadquartersId(1L);
        routeHistory.setCustomerId(0L);
        routeHistory.setVehicleTypeId(2);
        routeHistory.setValidFrom(ORDER_TIME.minusDays(1));
        routeHistory.setPrice(new BigDecimal("100"));

        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 100L, 1L, 2, ORDER_TIME)).thenReturn(List.of());
        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 0L, 1L, 2, ORDER_TIME)).thenReturn(List.of(routeHistory));

        TourDetailDto pickup = new TourDetailDto();
        pickup.setSort(1);
        pickup.setZipcode("20095");
        TourDetailDto delivery = new TourDetailDto();
        delivery.setSort(2);
        delivery.setZipcode("10115");

        JobPricingResultDto result = jobPricingService.calculateDraft(
                job,
                List.of(
                        price(JobPricingService.JB_CR_MARKUP, "5"),
                        price(JobPricingService.JB_CR_FIXSERVICEPRICE, "7.50")
                ),
                List.of(pickup, delivery),
                true
        );

        assertThat(result.getJob().getSubtotalPrice()).isEqualByComparingTo("110.00");
        assertThat(result.getJob().getTotalPrice()).isEqualByComparingTo("110.00");
        assertThat(result.getJob().getCourierSubprice()).isEqualByComparingTo("54.00");
        assertThat(result.getJob().getCourierPrice()).isEqualByComparingTo("64.20");
        assertThat(findPrice(result, JobPricingService.JB_CR_SUBTOTAL)).isEqualByComparingTo("61.50");
        assertThat(findPrice(result, JobPricingService.JB_CR_FIXSERVICEPRICE)).isEqualByComparingTo("7.50");
        assertThat(hasPrice(result, JobPricingService.JB_CR_PROV)).isFalse();
    }

    @Test
    void calculateDraftUsesZipCodeMatrixAndGlobalCourierDiscount() {
        Job job = new Job();
        job.setHeadquartersId(1L);
        job.setCostCenterPayerId(10L);
        job.setOrderTime(ORDER_TIME);
        job.setVehicleTypeId(2);
        job.setServicePrice(new BigDecimal("10"));
        job.setMarkup(new BigDecimal("10"));

        stubCustomer(BigDecimal.ZERO, BigDecimal.ZERO, 1);
        stubParameter(ParameterKeys.INV_SERVICEPRICE_DISCOUNT, 1L, "0");

        MetaType vehicleType = new MetaType();
        vehicleType.setSort(2);
        vehicleType.setType("vehicletype");
        vehicleType.setValue("PKW");
        when(metaTypeRepository.findBySortAndType(2, "vehicletype")).thenReturn(Optional.of(vehicleType));

        ServiceType serviceType = new ServiceType();
        serviceType.setId(501L);
        serviceType.setName("PKW");
        serviceType.setMode(0);
        serviceType.setHeadquartersId(1L);

        ServiceEntity basicService = new ServiceEntity();
        basicService.setId(601L);
        basicService.setName("Grundpreis");
        basicService.setMode(0);
        basicService.setServiceTypeId(501L);
        basicService.setHeadquartersId(1L);

        ServiceEntity globalCourierDiscount = new ServiceEntity();
        globalCourierDiscount.setId(602L);
        globalCourierDiscount.setName("Globaler<br>Frachtverg.-Abschlag [%]");
        globalCourierDiscount.setMode(0);
        globalCourierDiscount.setServiceTypeId(501L);
        globalCourierDiscount.setHeadquartersId(1L);

        when(serviceTypeRepository.findAvailableForCustomer(1L, 100L)).thenReturn(List.of(serviceType));
        when(serviceEntityRepository.findAvailableForCustomer(1L, 100L)).thenReturn(List.of(basicService, globalCourierDiscount));

        ServiceHistory basicHistory = new ServiceHistory();
        basicHistory.setServiceId(601L);
        basicHistory.setServiceTypeId(501L);
        basicHistory.setHeadquartersId(1L);
        basicHistory.setCustomerId(0L);
        basicHistory.setValidFrom(ORDER_TIME.minusDays(1));
        basicHistory.setPrice(new BigDecimal("20"));

        ServiceHistory globalDiscountHistory = new ServiceHistory();
        globalDiscountHistory.setServiceId(602L);
        globalDiscountHistory.setServiceTypeId(501L);
        globalDiscountHistory.setHeadquartersId(1L);
        globalDiscountHistory.setCustomerId(0L);
        globalDiscountHistory.setValidFrom(ORDER_TIME.minusDays(1));
        globalDiscountHistory.setCourierPrice(new BigDecimal("25"));

        when(serviceHistoryRepository.findLatestPriceForType(601L, 501L, 100L, 1L, ORDER_TIME)).thenReturn(List.of());
        when(serviceHistoryRepository.findLatestPriceForType(601L, 501L, 0L, 1L, ORDER_TIME)).thenReturn(List.of(basicHistory));
        when(serviceHistoryRepository.findLatestPriceForType(602L, 501L, 100L, 1L, ORDER_TIME)).thenReturn(List.of());
        when(serviceHistoryRepository.findLatestPriceForType(602L, 501L, 0L, 1L, ORDER_TIME)).thenReturn(List.of(globalDiscountHistory));

        ServiceZipCode fromZipCode = new ServiceZipCode();
        fromZipCode.setId(1L);
        fromZipCode.setZipcode("20095");
        ServiceZipCode toZipCode = new ServiceZipCode();
        toZipCode.setId(2L);
        toZipCode.setZipcode("10115");
        when(serviceZipCodeRepository.findByZipcode("20095")).thenReturn(Optional.of(fromZipCode));
        when(serviceZipCodeRepository.findByZipcode("10115")).thenReturn(Optional.of(toZipCode));

        ServiceZipCodeHistory routeHistory = new ServiceZipCodeHistory();
        routeHistory.setFromZipCodeId(1L);
        routeHistory.setToZipCodeId(2L);
        routeHistory.setHeadquartersId(1L);
        routeHistory.setCustomerId(0L);
        routeHistory.setVehicleTypeId(2);
        routeHistory.setValidFrom(ORDER_TIME.minusDays(1));
        routeHistory.setPrice(new BigDecimal("100"));
        routeHistory.setDiscount(new BigDecimal("5"));

        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 100L, 1L, 2, ORDER_TIME)).thenReturn(List.of());
        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 100L, 1L, 0, ORDER_TIME)).thenReturn(List.of());
        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 0L, 1L, 2, ORDER_TIME)).thenReturn(List.of(routeHistory));

        TourDetailDto pickup = new TourDetailDto();
        pickup.setSort(1);
        pickup.setZipcode("20095");
        TourDetailDto delivery = new TourDetailDto();
        delivery.setSort(2);
        delivery.setZipcode("10115");

        JobPricingResultDto result = jobPricingService.calculateDraft(job, List.of(), List.of(pickup, delivery));

        assertThat(result.getJob().getSubtotalPrice()).isEqualByComparingTo("125.00");
        assertThat(result.getJob().getTotalPrice()).isEqualByComparingTo("136.50");
        assertThat(result.getJob().getCourierSubprice()).isEqualByComparingTo("86.25");
        assertThat(result.getJob().getCourierPrice()).isEqualByComparingTo("93.75");
        assertThat(findPrice(result, JobPricingService.JB_CR_PROV)).isEqualByComparingTo("25.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_SERVICEPRICE)).isEqualByComparingTo("7.50");
    }

    @Test
    void calculateDraftAppliesNegativeCustomerCorrectionsForRouteMatrix() {
        Job job = new Job();
        job.setHeadquartersId(1L);
        job.setCostCenterPayerId(10L);
        job.setOrderTime(ORDER_TIME);
        job.setVehicleTypeId(2);
        job.setMarkup(BigDecimal.ZERO);

        stubCustomer(BigDecimal.ZERO, BigDecimal.ZERO, 1);
        stubParameter(ParameterKeys.INV_SERVICEPRICE_DISCOUNT, 1L, "0");

        MetaType vehicleType = new MetaType();
        vehicleType.setSort(2);
        vehicleType.setType("vehicletype");
        vehicleType.setValue("PKW");
        when(metaTypeRepository.findBySortAndType(2, "vehicletype")).thenReturn(Optional.of(vehicleType));

        ServiceType serviceType = new ServiceType();
        serviceType.setId(501L);
        serviceType.setName("PKW");
        serviceType.setMode(0);
        serviceType.setHeadquartersId(1L);

        ServiceEntity basicService = new ServiceEntity();
        basicService.setId(601L);
        basicService.setName("Grundpreis");
        basicService.setMode(0);
        basicService.setServiceTypeId(501L);
        basicService.setHeadquartersId(1L);

        ServiceEntity globalCourierDiscount = new ServiceEntity();
        globalCourierDiscount.setId(602L);
        globalCourierDiscount.setName("Globaler<br>Frachtverg.-Abschlag [%]");
        globalCourierDiscount.setMode(0);
        globalCourierDiscount.setServiceTypeId(501L);
        globalCourierDiscount.setHeadquartersId(1L);

        when(serviceTypeRepository.findAvailableForCustomer(1L, 100L)).thenReturn(List.of(serviceType));
        when(serviceEntityRepository.findAvailableForCustomer(1L, 100L)).thenReturn(List.of(basicService, globalCourierDiscount));

        ServiceHistory basicHistory = new ServiceHistory();
        basicHistory.setServiceId(601L);
        basicHistory.setServiceTypeId(501L);
        basicHistory.setHeadquartersId(1L);
        basicHistory.setCustomerId(0L);
        basicHistory.setValidFrom(ORDER_TIME.minusDays(1));
        basicHistory.setPrice(new BigDecimal("0.01"));

        ServiceHistory globalDiscountHistory = new ServiceHistory();
        globalDiscountHistory.setServiceId(602L);
        globalDiscountHistory.setServiceTypeId(501L);
        globalDiscountHistory.setHeadquartersId(1L);
        globalDiscountHistory.setCustomerId(0L);
        globalDiscountHistory.setValidFrom(ORDER_TIME.minusDays(1));
        globalDiscountHistory.setCourierPrice(new BigDecimal("25"));

        when(serviceHistoryRepository.findLatestPriceForType(601L, 501L, 100L, 1L, ORDER_TIME)).thenReturn(List.of());
        when(serviceHistoryRepository.findLatestPriceForType(601L, 501L, 0L, 1L, ORDER_TIME)).thenReturn(List.of(basicHistory));
        when(serviceHistoryRepository.findLatestPriceForType(602L, 501L, 100L, 1L, ORDER_TIME)).thenReturn(List.of());
        when(serviceHistoryRepository.findLatestPriceForType(602L, 501L, 0L, 1L, ORDER_TIME)).thenReturn(List.of(globalDiscountHistory));

        ServiceZipCode fromZipCode = new ServiceZipCode();
        fromZipCode.setId(1L);
        fromZipCode.setZipcode("20095");
        ServiceZipCode toZipCode = new ServiceZipCode();
        toZipCode.setId(2L);
        toZipCode.setZipcode("10115");
        when(serviceZipCodeRepository.findByZipcode("20095")).thenReturn(Optional.of(fromZipCode));
        when(serviceZipCodeRepository.findByZipcode("10115")).thenReturn(Optional.of(toZipCode));

        ServiceZipCodeHistory customerRouteHistory = new ServiceZipCodeHistory();
        customerRouteHistory.setFromZipCodeId(1L);
        customerRouteHistory.setToZipCodeId(2L);
        customerRouteHistory.setHeadquartersId(1L);
        customerRouteHistory.setCustomerId(100L);
        customerRouteHistory.setVehicleTypeId(2);
        customerRouteHistory.setValidFrom(ORDER_TIME.minusDays(1));
        customerRouteHistory.setPrice(new BigDecimal("-10"));
        customerRouteHistory.setDiscount(new BigDecimal("-2"));

        ServiceZipCodeHistory commonRouteHistory = new ServiceZipCodeHistory();
        commonRouteHistory.setFromZipCodeId(1L);
        commonRouteHistory.setToZipCodeId(2L);
        commonRouteHistory.setHeadquartersId(1L);
        commonRouteHistory.setCustomerId(0L);
        commonRouteHistory.setVehicleTypeId(2);
        commonRouteHistory.setValidFrom(ORDER_TIME.minusDays(1));
        commonRouteHistory.setPrice(new BigDecimal("100"));
        commonRouteHistory.setDiscount(new BigDecimal("5"));

        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 100L, 1L, 2, ORDER_TIME)).thenReturn(List.of(customerRouteHistory));
        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 0L, 1L, 2, ORDER_TIME)).thenReturn(List.of(commonRouteHistory));

        TourDetailDto pickup = new TourDetailDto();
        pickup.setSort(1);
        pickup.setZipcode("20095");
        TourDetailDto delivery = new TourDetailDto();
        delivery.setSort(2);
        delivery.setZipcode("10115");

        JobPricingResultDto result = jobPricingService.calculateDraft(job, List.of(), List.of(pickup, delivery));

        assertThat(result.getJob().getSubtotalPrice()).isEqualByComparingTo("87.30");
        assertThat(result.getJob().getTotalPrice()).isEqualByComparingTo("87.30");
        assertThat(result.getJob().getCourierSubprice()).isEqualByComparingTo("65.48");
        assertThat(result.getJob().getCourierPrice()).isEqualByComparingTo("65.48");
        assertThat(findPrice(result, JobPricingService.JB_CR_PROV)).isEqualByComparingTo("24.99");
    }

    @Test
    void calculateDraftUsesExactAddressAreaBeforeZipFallback() {
        Job job = new Job();
        job.setHeadquartersId(1L);
        job.setCostCenterPayerId(10L);
        job.setOrderTime(ORDER_TIME);
        job.setVehicleTypeId(2);
        job.setMarkup(BigDecimal.ZERO);

        stubCustomer(BigDecimal.ZERO, BigDecimal.ZERO, 2);
        stubParameter(ParameterKeys.INV_SERVICEPRICE_DISCOUNT, 1L, "0");
        stubParameter(ParameterKeys.MASK_AREA_ID_FROM_ADDRESS + "200", 0L, "12a,22");

        MetaType vehicleType = new MetaType();
        vehicleType.setSort(2);
        vehicleType.setType("vehicletype");
        vehicleType.setValue("PKW");
        when(metaTypeRepository.findBySortAndType(2, "vehicletype")).thenReturn(Optional.of(vehicleType));

        ServiceType serviceType = new ServiceType();
        serviceType.setId(501L);
        serviceType.setName("PKW");
        serviceType.setMode(0);
        serviceType.setHeadquartersId(1L);

        ServiceEntity basicService = new ServiceEntity();
        basicService.setId(601L);
        basicService.setName("Grundpreis");
        basicService.setMode(0);
        basicService.setServiceTypeId(501L);
        basicService.setHeadquartersId(1L);

        ServiceEntity globalCourierDiscount = new ServiceEntity();
        globalCourierDiscount.setId(602L);
        globalCourierDiscount.setName("Globaler<br>Frachtverg.-Abschlag [%]");
        globalCourierDiscount.setMode(0);
        globalCourierDiscount.setServiceTypeId(501L);
        globalCourierDiscount.setHeadquartersId(1L);

        when(serviceTypeRepository.findAvailableForCustomer(1L, 100L)).thenReturn(List.of(serviceType));
        when(serviceEntityRepository.findAvailableForCustomer(1L, 100L)).thenReturn(List.of(basicService, globalCourierDiscount));

        ServiceHistory basicHistory = new ServiceHistory();
        basicHistory.setServiceId(601L);
        basicHistory.setServiceTypeId(501L);
        basicHistory.setHeadquartersId(1L);
        basicHistory.setCustomerId(0L);
        basicHistory.setValidFrom(ORDER_TIME.minusDays(1));
        basicHistory.setPrice(new BigDecimal("0.01"));

        ServiceHistory globalDiscountHistory = new ServiceHistory();
        globalDiscountHistory.setServiceId(602L);
        globalDiscountHistory.setServiceTypeId(501L);
        globalDiscountHistory.setHeadquartersId(1L);
        globalDiscountHistory.setCustomerId(0L);
        globalDiscountHistory.setValidFrom(ORDER_TIME.minusDays(1));
        globalDiscountHistory.setCourierPrice(new BigDecimal("20"));

        when(serviceHistoryRepository.findLatestPriceForType(601L, 501L, 100L, 1L, ORDER_TIME)).thenReturn(List.of());
        when(serviceHistoryRepository.findLatestPriceForType(601L, 501L, 0L, 1L, ORDER_TIME)).thenReturn(List.of(basicHistory));
        when(serviceHistoryRepository.findLatestPriceForType(602L, 501L, 100L, 1L, ORDER_TIME)).thenReturn(List.of());
        when(serviceHistoryRepository.findLatestPriceForType(602L, 501L, 0L, 1L, ORDER_TIME)).thenReturn(List.of(globalDiscountHistory));

        ServiceZipCode fromZipCode = new ServiceZipCode();
        fromZipCode.setId(1L);
        fromZipCode.setZipcode("20095");
        ServiceZipCode toZipCode = new ServiceZipCode();
        toZipCode.setId(2L);
        toZipCode.setZipcode("10115");
        when(serviceZipCodeRepository.findByZipcode("20095")).thenReturn(Optional.of(fromZipCode));
        when(serviceZipCodeRepository.findByZipcode("10115")).thenReturn(Optional.of(toZipCode));

        ServiceZipCodeAreaAddress pickupArea = new ServiceZipCodeAreaAddress();
        pickupArea.setId(200L);
        pickupArea.setAreaId(11L);
        pickupArea.setHouseNumberFrom("10");
        pickupArea.setHouseNumberTo("20");
        pickupArea.setMode(0);
        ServiceZipCodeAreaAddress deliveryArea = new ServiceZipCodeAreaAddress();
        deliveryArea.setId(201L);
        deliveryArea.setAreaId(33L);
        deliveryArea.setHouseNumberFrom("1");
        deliveryArea.setHouseNumberTo("99");
        deliveryArea.setMode(0);

        when(serviceZipCodeAreaAddressRepository.findAddressCandidates(1L, "20095", "Musterstrasse")).thenReturn(List.of(pickupArea));
        when(serviceZipCodeAreaAddressRepository.findAddressCandidates(1L, "10115", "Lieferweg")).thenReturn(List.of(deliveryArea));

        ServiceZipCodeAreaHistory areaHistory = new ServiceZipCodeAreaHistory();
        areaHistory.setFromAreaId(22L);
        areaHistory.setToAreaId(33L);
        areaHistory.setHeadquartersId(1L);
        areaHistory.setCustomerId(0L);
        areaHistory.setVehicleTypeId(2);
        areaHistory.setValidFrom(ORDER_TIME.minusDays(1));
        areaHistory.setPrice(new BigDecimal("40"));

        when(serviceZipCodeAreaHistoryRepository.findLatest(22L, 33L, 100L, 1L, 2, ORDER_TIME)).thenReturn(List.of());
        when(serviceZipCodeAreaHistoryRepository.findLatest(22L, 33L, 0L, 1L, 2, ORDER_TIME)).thenReturn(List.of(areaHistory));

        TourDetailDto pickup = new TourDetailDto();
        pickup.setSort(1);
        pickup.setStreet("Musterstrasse");
        pickup.setHsno("12a");
        pickup.setZipcode("20095");

        TourDetailDto delivery = new TourDetailDto();
        delivery.setSort(2);
        delivery.setStreet("Lieferweg");
        delivery.setHsno("8");
        delivery.setZipcode("10115");

        JobPricingResultDto result = jobPricingService.calculateDraft(job, List.of(), List.of(pickup, delivery));

        assertThat(result.getJob().getSubtotalPrice()).isEqualByComparingTo("40.00");
        assertThat(result.getJob().getTotalPrice()).isEqualByComparingTo("40.00");
        assertThat(result.getJob().getCourierSubprice()).isEqualByComparingTo("32.00");
        assertThat(result.getJob().getCourierPrice()).isEqualByComparingTo("32.00");
    }

    @Test
    void calculateDraftUsesServiceFuelMarkupForRouteTotals() {
        Job job = new Job();
        job.setHeadquartersId(1L);
        job.setCostCenterPayerId(10L);
        job.setOrderTime(ORDER_TIME);
        job.setVehicleTypeId(2);

        stubCustomer(BigDecimal.ZERO, BigDecimal.ZERO, 1, 1, BigDecimal.ZERO);
        stubParameter(ParameterKeys.INV_SERVICEPRICE_DISCOUNT, 1L, "0");

        MetaType vehicleType = new MetaType();
        vehicleType.setSort(2);
        vehicleType.setType("vehicletype");
        vehicleType.setValue("PKW");
        when(metaTypeRepository.findBySortAndType(2, "vehicletype")).thenReturn(Optional.of(vehicleType));

        ServiceType serviceType = new ServiceType();
        serviceType.setId(501L);
        serviceType.setName("PKW");
        serviceType.setMode(0);
        serviceType.setHeadquartersId(1L);

        ServiceEntity basicService = new ServiceEntity();
        basicService.setId(601L);
        basicService.setName("Grundpreis");
        basicService.setMode(0);
        basicService.setServiceTypeId(501L);
        basicService.setHeadquartersId(1L);

        ServiceEntity globalCourierDiscount = new ServiceEntity();
        globalCourierDiscount.setId(602L);
        globalCourierDiscount.setName("Globaler<br>Frachtverg.-Abschlag [%]");
        globalCourierDiscount.setMode(0);
        globalCourierDiscount.setServiceTypeId(501L);
        globalCourierDiscount.setHeadquartersId(1L);

        ServiceEntity fuelMarkupService = new ServiceEntity();
        fuelMarkupService.setId(603L);
        fuelMarkupService.setName("Aufschlag<br>Sprit [%]");
        fuelMarkupService.setMode(0);
        fuelMarkupService.setServiceTypeId(501L);
        fuelMarkupService.setHeadquartersId(1L);

        when(serviceTypeRepository.findAvailableForCustomer(1L, 100L)).thenReturn(List.of(serviceType));
        when(serviceEntityRepository.findAvailableForCustomer(1L, 100L)).thenReturn(List.of(basicService, globalCourierDiscount, fuelMarkupService));

        ServiceHistory basicHistory = new ServiceHistory();
        basicHistory.setServiceId(601L);
        basicHistory.setServiceTypeId(501L);
        basicHistory.setHeadquartersId(1L);
        basicHistory.setCustomerId(0L);
        basicHistory.setValidFrom(ORDER_TIME.minusDays(1));
        basicHistory.setPrice(new BigDecimal("20"));

        ServiceHistory globalDiscountHistory = new ServiceHistory();
        globalDiscountHistory.setServiceId(602L);
        globalDiscountHistory.setServiceTypeId(501L);
        globalDiscountHistory.setHeadquartersId(1L);
        globalDiscountHistory.setCustomerId(0L);
        globalDiscountHistory.setValidFrom(ORDER_TIME.minusDays(1));
        globalDiscountHistory.setCourierPrice(new BigDecimal("25"));

        ServiceHistory fuelMarkupHistory = new ServiceHistory();
        fuelMarkupHistory.setServiceId(603L);
        fuelMarkupHistory.setServiceTypeId(501L);
        fuelMarkupHistory.setHeadquartersId(1L);
        fuelMarkupHistory.setCustomerId(0L);
        fuelMarkupHistory.setValidFrom(ORDER_TIME.minusDays(1));
        fuelMarkupHistory.setPrice(new BigDecimal("10"));
        fuelMarkupHistory.setDiscount(new BigDecimal("20"));
        fuelMarkupHistory.setCourierPrice(new BigDecimal("5"));

        when(serviceHistoryRepository.findLatestPriceForType(601L, 501L, 100L, 1L, ORDER_TIME)).thenReturn(List.of());
        when(serviceHistoryRepository.findLatestPriceForType(601L, 501L, 0L, 1L, ORDER_TIME)).thenReturn(List.of(basicHistory));
        when(serviceHistoryRepository.findLatestPriceForType(602L, 501L, 100L, 1L, ORDER_TIME)).thenReturn(List.of());
        when(serviceHistoryRepository.findLatestPriceForType(602L, 501L, 0L, 1L, ORDER_TIME)).thenReturn(List.of(globalDiscountHistory));
        when(serviceHistoryRepository.findLatestPriceForType(603L, 501L, 100L, 1L, ORDER_TIME)).thenReturn(List.of());
        when(serviceHistoryRepository.findLatestPriceForType(603L, 501L, 0L, 1L, ORDER_TIME)).thenReturn(List.of(fuelMarkupHistory));

        ServiceZipCode fromZipCode = new ServiceZipCode();
        fromZipCode.setId(1L);
        fromZipCode.setZipcode("20095");
        ServiceZipCode toZipCode = new ServiceZipCode();
        toZipCode.setId(2L);
        toZipCode.setZipcode("10115");
        when(serviceZipCodeRepository.findByZipcode("20095")).thenReturn(Optional.of(fromZipCode));
        when(serviceZipCodeRepository.findByZipcode("10115")).thenReturn(Optional.of(toZipCode));

        ServiceZipCodeHistory routeHistory = new ServiceZipCodeHistory();
        routeHistory.setFromZipCodeId(1L);
        routeHistory.setToZipCodeId(2L);
        routeHistory.setHeadquartersId(1L);
        routeHistory.setCustomerId(0L);
        routeHistory.setVehicleTypeId(2);
        routeHistory.setValidFrom(ORDER_TIME.minusDays(1));
        routeHistory.setPrice(new BigDecimal("100"));

        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 100L, 1L, 2, ORDER_TIME)).thenReturn(List.of());
        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 0L, 1L, 2, ORDER_TIME)).thenReturn(List.of(routeHistory));

        TourDetailDto pickup = new TourDetailDto();
        pickup.setSort(1);
        pickup.setZipcode("20095");
        TourDetailDto delivery = new TourDetailDto();
        delivery.setSort(2);
        delivery.setZipcode("10115");

        JobPricingResultDto result = jobPricingService.calculateDraft(job, List.of(), List.of(pickup, delivery));

        assertThat(result.getJob().getMarkup()).isEqualByComparingTo("8.00");
        assertThat(result.getJob().getSubtotalPrice()).isEqualByComparingTo("120.00");
        assertThat(result.getJob().getTotalPrice()).isEqualByComparingTo("129.60");
        assertThat(result.getJob().getCourierSubprice()).isEqualByComparingTo("90.00");
        assertThat(result.getJob().getCourierPrice()).isEqualByComparingTo("94.50");
        assertThat(findPrice(result, JobPricingService.JB_CR_PROV)).isEqualByComparingTo("25.00");
        assertThat(result.getTourServices())
                .extracting(row -> row.getTourSort(), row -> row.getServiceName(), row -> row.getServiceTypeName(), row -> row.getPrice(), row -> row.getDiscount())
                .containsExactly(
                        org.assertj.core.groups.Tuple.tuple(0, "Grundpreis", "PKW", new BigDecimal("21.60"), new BigDecimal("0.00")),
                        org.assertj.core.groups.Tuple.tuple(1, "PLZ 20095 nach PLZ 10115", "", new BigDecimal("108.00"), new BigDecimal("0.00"))
                );
    }

    @Test
    void calculateDraftBuildsAreaLabelsForPersistedTourServiceRows() {
        Job job = new Job();
        job.setHeadquartersId(1L);
        job.setCostCenterPayerId(10L);
        job.setOrderTime(ORDER_TIME);
        job.setVehicleTypeId(2);
        job.setMarkup(new BigDecimal("10"));

        stubCustomer(BigDecimal.ZERO, BigDecimal.ZERO, 2);
        stubParameter(ParameterKeys.INV_SERVICEPRICE_DISCOUNT, 1L, "0");

        MetaType vehicleType = new MetaType();
        vehicleType.setSort(2);
        vehicleType.setType("vehicletype");
        vehicleType.setValue("PKW");
        when(metaTypeRepository.findBySortAndType(2, "vehicletype")).thenReturn(Optional.of(vehicleType));

        ServiceType serviceType = new ServiceType();
        serviceType.setId(501L);
        serviceType.setName("PKW");
        serviceType.setMode(0);
        serviceType.setHeadquartersId(1L);

        ServiceEntity basicService = new ServiceEntity();
        basicService.setId(601L);
        basicService.setName("Grundpreis");
        basicService.setMode(0);
        basicService.setServiceTypeId(501L);
        basicService.setHeadquartersId(1L);

        when(serviceTypeRepository.findAvailableForCustomer(1L, 100L)).thenReturn(List.of(serviceType));
        when(serviceEntityRepository.findAvailableForCustomer(1L, 100L)).thenReturn(List.of(basicService));

        ServiceHistory basicHistory = new ServiceHistory();
        basicHistory.setServiceId(601L);
        basicHistory.setServiceTypeId(501L);
        basicHistory.setHeadquartersId(1L);
        basicHistory.setCustomerId(0L);
        basicHistory.setValidFrom(ORDER_TIME.minusDays(1));
        basicHistory.setPrice(new BigDecimal("10"));
        when(serviceHistoryRepository.findLatestPriceForType(601L, 501L, 100L, 1L, ORDER_TIME)).thenReturn(List.of());
        when(serviceHistoryRepository.findLatestPriceForType(601L, 501L, 0L, 1L, ORDER_TIME)).thenReturn(List.of(basicHistory));

        ServiceZipCode fromZipCode = new ServiceZipCode();
        fromZipCode.setId(1L);
        fromZipCode.setZipcode("20095");
        ServiceZipCode toZipCode = new ServiceZipCode();
        toZipCode.setId(2L);
        toZipCode.setZipcode("10115");
        when(serviceZipCodeRepository.findByZipcode("20095")).thenReturn(Optional.of(fromZipCode));
        when(serviceZipCodeRepository.findByZipcode("10115")).thenReturn(Optional.of(toZipCode));
        when(serviceZipCodeAreaMappingRepository.findAreaIdByZipCodeIdAndHeadquartersId(1L, 1L)).thenReturn(Optional.of(11L));
        when(serviceZipCodeAreaMappingRepository.findAreaIdByZipCodeIdAndHeadquartersId(2L, 1L)).thenReturn(Optional.of(12L));

        ServiceZipCodeAreaHistory areaHistory = new ServiceZipCodeAreaHistory();
        areaHistory.setFromAreaId(11L);
        areaHistory.setToAreaId(12L);
        areaHistory.setHeadquartersId(1L);
        areaHistory.setCustomerId(0L);
        areaHistory.setVehicleTypeId(2);
        areaHistory.setValidFrom(ORDER_TIME.minusDays(1));
        areaHistory.setPrice(new BigDecimal("100"));
        when(serviceZipCodeAreaHistoryRepository.findLatest(11L, 12L, 100L, 1L, 2, ORDER_TIME)).thenReturn(List.of());
        when(serviceZipCodeAreaHistoryRepository.findLatest(11L, 12L, 0L, 1L, 2, ORDER_TIME)).thenReturn(List.of(areaHistory));

        ServiceZipCodeArea fromArea = new ServiceZipCodeArea();
        fromArea.setId(11L);
        fromArea.setName("A1");
        fromArea.setLongname("Mitte");
        ServiceZipCodeArea toArea = new ServiceZipCodeArea();
        toArea.setId(12L);
        toArea.setName("B2");
        toArea.setLongname("Nord");
        when(serviceZipCodeAreaRepository.findById(11L)).thenReturn(Optional.of(fromArea));
        when(serviceZipCodeAreaRepository.findById(12L)).thenReturn(Optional.of(toArea));

        TourDetailDto pickup = new TourDetailDto();
        pickup.setSort(1);
        pickup.setZipcode("20095");
        TourDetailDto delivery = new TourDetailDto();
        delivery.setSort(2);
        delivery.setZipcode("10115");

        JobPricingResultDto result = jobPricingService.calculateDraft(job, List.of(), List.of(pickup, delivery));

        assertThat(result.getTourServices())
                .extracting(row -> row.getTourSort(), row -> row.getServiceName(), row -> row.getServiceTypeName(), row -> row.getPrice(), row -> row.getDiscount())
                .containsExactly(
                        org.assertj.core.groups.Tuple.tuple(0, "Grundpreis", "PKW", new BigDecimal("11.00"), new BigDecimal("0.00")),
                        org.assertj.core.groups.Tuple.tuple(1, "A1 (Mitte) nach B2 (Nord)", "", new BigDecimal("110.00"), new BigDecimal("0.00"))
                );
    }

    @Test
    void calculateDraftUsesRouteCourierPriceRateServiceWhenAvailable() {
        Job job = new Job();
        job.setHeadquartersId(1L);
        job.setCostCenterPayerId(10L);
        job.setOrderTime(ORDER_TIME);
        job.setVehicleTypeId(2);
        job.setServicePrice(new BigDecimal("10"));
        job.setMarkup(new BigDecimal("10"));

        stubCustomer(BigDecimal.ZERO, BigDecimal.ZERO, 1);
        stubParameter(ParameterKeys.INV_SERVICEPRICE_DISCOUNT, 1L, "0");

        MetaType vehicleType = new MetaType();
        vehicleType.setSort(2);
        vehicleType.setType("vehicletype");
        vehicleType.setValue("PKW");
        when(metaTypeRepository.findBySortAndType(2, "vehicletype")).thenReturn(Optional.of(vehicleType));

        ServiceType serviceType = new ServiceType();
        serviceType.setId(501L);
        serviceType.setName("PKW");
        serviceType.setMode(0);
        serviceType.setHeadquartersId(1L);

        ServiceEntity basicService = new ServiceEntity();
        basicService.setId(601L);
        basicService.setName("Grundpreis");
        basicService.setMode(0);
        basicService.setServiceTypeId(501L);
        basicService.setHeadquartersId(1L);

        ServiceEntity globalCourierDiscount = new ServiceEntity();
        globalCourierDiscount.setId(602L);
        globalCourierDiscount.setName("Globaler<br>Frachtverg.-Abschlag [%]");
        globalCourierDiscount.setMode(0);
        globalCourierDiscount.setServiceTypeId(501L);
        globalCourierDiscount.setHeadquartersId(1L);

        ServiceEntity globalCourierPriceRate = new ServiceEntity();
        globalCourierPriceRate.setId(603L);
        globalCourierPriceRate.setName("Globaler<br>Fuhrlohnabschlag [%]");
        globalCourierPriceRate.setMode(0);
        globalCourierPriceRate.setServiceTypeId(501L);
        globalCourierPriceRate.setHeadquartersId(1L);

        when(serviceTypeRepository.findAvailableForCustomer(1L, 100L)).thenReturn(List.of(serviceType));
        when(serviceEntityRepository.findAvailableForCustomer(1L, 100L))
                .thenReturn(List.of(basicService, globalCourierDiscount, globalCourierPriceRate));

        ServiceHistory basicHistory = new ServiceHistory();
        basicHistory.setServiceId(601L);
        basicHistory.setServiceTypeId(501L);
        basicHistory.setHeadquartersId(1L);
        basicHistory.setCustomerId(0L);
        basicHistory.setValidFrom(ORDER_TIME.minusDays(1));
        basicHistory.setPrice(new BigDecimal("20"));

        ServiceHistory globalDiscountHistory = new ServiceHistory();
        globalDiscountHistory.setServiceId(602L);
        globalDiscountHistory.setServiceTypeId(501L);
        globalDiscountHistory.setHeadquartersId(1L);
        globalDiscountHistory.setCustomerId(0L);
        globalDiscountHistory.setValidFrom(ORDER_TIME.minusDays(1));
        globalDiscountHistory.setCourierPrice(new BigDecimal("25"));

        ServiceHistory globalCourierPriceRateHistory = new ServiceHistory();
        globalCourierPriceRateHistory.setServiceId(603L);
        globalCourierPriceRateHistory.setServiceTypeId(501L);
        globalCourierPriceRateHistory.setHeadquartersId(1L);
        globalCourierPriceRateHistory.setCustomerId(0L);
        globalCourierPriceRateHistory.setValidFrom(ORDER_TIME.minusDays(1));
        globalCourierPriceRateHistory.setCourierPrice(new BigDecimal("30"));

        when(serviceHistoryRepository.findLatestPriceForType(601L, 501L, 100L, 1L, ORDER_TIME)).thenReturn(List.of());
        when(serviceHistoryRepository.findLatestPriceForType(601L, 501L, 0L, 1L, ORDER_TIME)).thenReturn(List.of(basicHistory));
        when(serviceHistoryRepository.findLatestPriceForType(602L, 501L, 100L, 1L, ORDER_TIME)).thenReturn(List.of());
        when(serviceHistoryRepository.findLatestPriceForType(602L, 501L, 0L, 1L, ORDER_TIME)).thenReturn(List.of(globalDiscountHistory));
        when(serviceHistoryRepository.findLatestPriceForType(603L, 501L, 100L, 1L, ORDER_TIME)).thenReturn(List.of());
        when(serviceHistoryRepository.findLatestPriceForType(603L, 501L, 0L, 1L, ORDER_TIME)).thenReturn(List.of(globalCourierPriceRateHistory));

        ServiceZipCode fromZipCode = new ServiceZipCode();
        fromZipCode.setId(1L);
        fromZipCode.setZipcode("20095");
        ServiceZipCode toZipCode = new ServiceZipCode();
        toZipCode.setId(2L);
        toZipCode.setZipcode("10115");
        when(serviceZipCodeRepository.findByZipcode("20095")).thenReturn(Optional.of(fromZipCode));
        when(serviceZipCodeRepository.findByZipcode("10115")).thenReturn(Optional.of(toZipCode));

        ServiceZipCodeHistory routeHistory = new ServiceZipCodeHistory();
        routeHistory.setFromZipCodeId(1L);
        routeHistory.setToZipCodeId(2L);
        routeHistory.setHeadquartersId(1L);
        routeHistory.setCustomerId(0L);
        routeHistory.setVehicleTypeId(2);
        routeHistory.setValidFrom(ORDER_TIME.minusDays(1));
        routeHistory.setPrice(new BigDecimal("100"));
        routeHistory.setDiscount(new BigDecimal("5"));

        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 100L, 1L, 2, ORDER_TIME)).thenReturn(List.of());
        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 100L, 1L, 0, ORDER_TIME)).thenReturn(List.of());
        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 0L, 1L, 2, ORDER_TIME)).thenReturn(List.of(routeHistory));

        TourDetailDto pickup = new TourDetailDto();
        pickup.setSort(1);
        pickup.setZipcode("20095");
        TourDetailDto delivery = new TourDetailDto();
        delivery.setSort(2);
        delivery.setZipcode("10115");

        JobPricingResultDto result = jobPricingService.calculateDraft(job, List.of(), List.of(pickup, delivery));

        assertThat(result.getJob().getSubtotalPrice()).isEqualByComparingTo("125.00");
        assertThat(result.getJob().getTotalPrice()).isEqualByComparingTo("136.50");
        assertThat(result.getJob().getCourierSubprice()).isEqualByComparingTo("86.25");
        assertThat(result.getJob().getCourierPrice()).isEqualByComparingTo("93.75");
        assertThat(findPrice(result, JobPricingService.JB_CR_PROV)).isEqualByComparingTo("30.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_SERVICEPRICE)).isEqualByComparingTo("7.50");
    }

    @Test
    void calculateDraftAddsCourierServiceToDerivedSubtotalAfterCrPriceModeDate() {
        Job job = new Job();
        job.setHeadquartersId(1L);
        job.setCostCenterPayerId(10L);
        job.setOrderTime(ORDER_TIME);
        job.setVehicleTypeId(2);
        job.setServicePrice(new BigDecimal("10"));
        job.setMarkup(new BigDecimal("10"));

        stubCustomer(BigDecimal.ZERO, BigDecimal.ZERO, 1);
        stubParameter(ParameterKeys.INV_SERVICEPRICE_DISCOUNT, 1L, "0");
        stubParameter(ParameterKeys.MASK_CR_PRICE_MODE, 1L, "1");
        stubParameter(ParameterKeys.MASK_CR_PRICE_MODE_DATE, 1L, "2023-04-01");

        MetaType vehicleType = new MetaType();
        vehicleType.setSort(2);
        vehicleType.setType("vehicletype");
        vehicleType.setValue("PKW");
        when(metaTypeRepository.findBySortAndType(2, "vehicletype")).thenReturn(Optional.of(vehicleType));

        ServiceType serviceType = new ServiceType();
        serviceType.setId(501L);
        serviceType.setName("PKW");
        serviceType.setMode(0);
        serviceType.setHeadquartersId(1L);

        ServiceEntity basicService = new ServiceEntity();
        basicService.setId(601L);
        basicService.setName("Grundpreis");
        basicService.setMode(0);
        basicService.setServiceTypeId(501L);
        basicService.setHeadquartersId(1L);

        ServiceEntity globalCourierDiscount = new ServiceEntity();
        globalCourierDiscount.setId(602L);
        globalCourierDiscount.setName("Globaler<br>Frachtverg.-Abschlag [%]");
        globalCourierDiscount.setMode(0);
        globalCourierDiscount.setServiceTypeId(501L);
        globalCourierDiscount.setHeadquartersId(1L);

        when(serviceTypeRepository.findAvailableForCustomer(1L, 100L)).thenReturn(List.of(serviceType));
        when(serviceEntityRepository.findAvailableForCustomer(1L, 100L)).thenReturn(List.of(basicService, globalCourierDiscount));

        ServiceHistory basicHistory = new ServiceHistory();
        basicHistory.setServiceId(601L);
        basicHistory.setServiceTypeId(501L);
        basicHistory.setHeadquartersId(1L);
        basicHistory.setCustomerId(0L);
        basicHistory.setValidFrom(ORDER_TIME.minusDays(1));
        basicHistory.setPrice(new BigDecimal("20"));

        ServiceHistory globalDiscountHistory = new ServiceHistory();
        globalDiscountHistory.setServiceId(602L);
        globalDiscountHistory.setServiceTypeId(501L);
        globalDiscountHistory.setHeadquartersId(1L);
        globalDiscountHistory.setCustomerId(0L);
        globalDiscountHistory.setValidFrom(ORDER_TIME.minusDays(1));
        globalDiscountHistory.setCourierPrice(new BigDecimal("25"));

        when(serviceHistoryRepository.findLatestPriceForType(601L, 501L, 100L, 1L, ORDER_TIME)).thenReturn(List.of());
        when(serviceHistoryRepository.findLatestPriceForType(601L, 501L, 0L, 1L, ORDER_TIME)).thenReturn(List.of(basicHistory));
        when(serviceHistoryRepository.findLatestPriceForType(602L, 501L, 100L, 1L, ORDER_TIME)).thenReturn(List.of());
        when(serviceHistoryRepository.findLatestPriceForType(602L, 501L, 0L, 1L, ORDER_TIME)).thenReturn(List.of(globalDiscountHistory));

        ServiceZipCode fromZipCode = new ServiceZipCode();
        fromZipCode.setId(1L);
        fromZipCode.setZipcode("20095");
        ServiceZipCode toZipCode = new ServiceZipCode();
        toZipCode.setId(2L);
        toZipCode.setZipcode("10115");
        when(serviceZipCodeRepository.findByZipcode("20095")).thenReturn(Optional.of(fromZipCode));
        when(serviceZipCodeRepository.findByZipcode("10115")).thenReturn(Optional.of(toZipCode));

        ServiceZipCodeHistory routeHistory = new ServiceZipCodeHistory();
        routeHistory.setFromZipCodeId(1L);
        routeHistory.setToZipCodeId(2L);
        routeHistory.setHeadquartersId(1L);
        routeHistory.setCustomerId(0L);
        routeHistory.setVehicleTypeId(2);
        routeHistory.setValidFrom(ORDER_TIME.minusDays(1));
        routeHistory.setPrice(new BigDecimal("100"));
        routeHistory.setDiscount(new BigDecimal("5"));

        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 100L, 1L, 2, ORDER_TIME)).thenReturn(List.of());
        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 100L, 1L, 0, ORDER_TIME)).thenReturn(List.of());
        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 0L, 1L, 2, ORDER_TIME)).thenReturn(List.of(routeHistory));

        TourDetailDto pickup = new TourDetailDto();
        pickup.setSort(1);
        pickup.setZipcode("20095");
        TourDetailDto delivery = new TourDetailDto();
        delivery.setSort(2);
        delivery.setZipcode("10115");

        JobPricingResultDto result = jobPricingService.calculateDraft(job, List.of(), List.of(pickup, delivery));

        assertThat(result.getJob().getCourierSubprice()).isEqualByComparingTo("86.25");
        assertThat(result.getJob().getCourierPrice()).isEqualByComparingTo("93.75");
        assertThat(findPrice(result, JobPricingService.JB_CR_SUBTOTAL)).isEqualByComparingTo("93.75");
        assertThat(findPrice(result, JobPricingService.JB_CR_SERVICEPRICE)).isEqualByComparingTo("7.50");
    }

    @Test
    void calculateDraftAppliesCourierAgioFromHistoryAfterCrPriceModeDate() {
        Job job = new Job();
        job.setHeadquartersId(1L);
        job.setCostCenterPayerId(10L);
        job.setCourierId(77L);
        job.setOrderTime(ORDER_TIME);
        job.setVehicleTypeId(2);
        job.setServicePrice(new BigDecimal("10"));
        job.setMarkup(new BigDecimal("10"));

        stubCustomer(BigDecimal.ZERO, BigDecimal.ZERO, 1);
        stubParameter(ParameterKeys.INV_SERVICEPRICE_DISCOUNT, 1L, "0");
        stubParameter(ParameterKeys.MASK_CR_PRICE_MODE, 1L, "1");
        stubParameter(ParameterKeys.MASK_CR_PRICE_MODE_DATE, 1L, "2023-04-01");

        MetaType vehicleType = new MetaType();
        vehicleType.setSort(2);
        vehicleType.setType("vehicletype");
        vehicleType.setValue("PKW");
        when(metaTypeRepository.findBySortAndType(2, "vehicletype")).thenReturn(Optional.of(vehicleType));

        ServiceType serviceType = new ServiceType();
        serviceType.setId(501L);
        serviceType.setName("PKW");
        serviceType.setMode(0);
        serviceType.setHeadquartersId(1L);

        ServiceEntity basicService = new ServiceEntity();
        basicService.setId(601L);
        basicService.setName("Grundpreis");
        basicService.setMode(0);
        basicService.setServiceTypeId(501L);
        basicService.setHeadquartersId(1L);

        ServiceEntity globalCourierDiscount = new ServiceEntity();
        globalCourierDiscount.setId(602L);
        globalCourierDiscount.setName("Globaler<br>Frachtverg.-Abschlag [%]");
        globalCourierDiscount.setMode(0);
        globalCourierDiscount.setServiceTypeId(501L);
        globalCourierDiscount.setHeadquartersId(1L);

        when(serviceTypeRepository.findAvailableForCustomer(1L, 100L)).thenReturn(List.of(serviceType));
        when(serviceEntityRepository.findAvailableForCustomer(1L, 100L)).thenReturn(List.of(basicService, globalCourierDiscount));

        ServiceHistory basicHistory = new ServiceHistory();
        basicHistory.setServiceId(601L);
        basicHistory.setServiceTypeId(501L);
        basicHistory.setHeadquartersId(1L);
        basicHistory.setCustomerId(0L);
        basicHistory.setValidFrom(ORDER_TIME.minusDays(1));
        basicHistory.setPrice(new BigDecimal("20"));

        ServiceHistory globalDiscountHistory = new ServiceHistory();
        globalDiscountHistory.setServiceId(602L);
        globalDiscountHistory.setServiceTypeId(501L);
        globalDiscountHistory.setHeadquartersId(1L);
        globalDiscountHistory.setCustomerId(0L);
        globalDiscountHistory.setValidFrom(ORDER_TIME.minusDays(1));
        globalDiscountHistory.setCourierPrice(new BigDecimal("25"));

        when(serviceHistoryRepository.findLatestPriceForType(601L, 501L, 100L, 1L, ORDER_TIME)).thenReturn(List.of());
        when(serviceHistoryRepository.findLatestPriceForType(601L, 501L, 0L, 1L, ORDER_TIME)).thenReturn(List.of(basicHistory));
        when(serviceHistoryRepository.findLatestPriceForType(602L, 501L, 100L, 1L, ORDER_TIME)).thenReturn(List.of());
        when(serviceHistoryRepository.findLatestPriceForType(602L, 501L, 0L, 1L, ORDER_TIME)).thenReturn(List.of(globalDiscountHistory));

        ServicePriceHistory agioHistory = new ServicePriceHistory();
        agioHistory.setObjectType("cr");
        agioHistory.setObjectId(77L);
        agioHistory.setFieldName("cscr_prov");
        agioHistory.setContext("100");
        agioHistory.setPrice(new BigDecimal("5"));
        agioHistory.setValidFrom(ORDER_TIME.minusDays(1));
        when(servicePriceHistoryRepository.findLatestFieldValueWithContext("cr", 77L, "cscr_prov", "100", ORDER_TIME))
                .thenReturn(List.of(agioHistory));

        ServiceZipCode fromZipCode = new ServiceZipCode();
        fromZipCode.setId(1L);
        fromZipCode.setZipcode("20095");
        ServiceZipCode toZipCode = new ServiceZipCode();
        toZipCode.setId(2L);
        toZipCode.setZipcode("10115");
        when(serviceZipCodeRepository.findByZipcode("20095")).thenReturn(Optional.of(fromZipCode));
        when(serviceZipCodeRepository.findByZipcode("10115")).thenReturn(Optional.of(toZipCode));

        ServiceZipCodeHistory routeHistory = new ServiceZipCodeHistory();
        routeHistory.setFromZipCodeId(1L);
        routeHistory.setToZipCodeId(2L);
        routeHistory.setHeadquartersId(1L);
        routeHistory.setCustomerId(0L);
        routeHistory.setVehicleTypeId(2);
        routeHistory.setValidFrom(ORDER_TIME.minusDays(1));
        routeHistory.setPrice(new BigDecimal("100"));
        routeHistory.setDiscount(new BigDecimal("5"));

        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 100L, 1L, 2, ORDER_TIME)).thenReturn(List.of());
        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 100L, 1L, 0, ORDER_TIME)).thenReturn(List.of());
        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 0L, 1L, 2, ORDER_TIME)).thenReturn(List.of(routeHistory));

        TourDetailDto pickup = new TourDetailDto();
        pickup.setSort(1);
        pickup.setZipcode("20095");
        TourDetailDto delivery = new TourDetailDto();
        delivery.setSort(2);
        delivery.setZipcode("10115");

        JobPricingResultDto result = jobPricingService.calculateDraft(job, List.of(), List.of(pickup, delivery));

        assertThat(result.getJob().getSubtotalPrice()).isEqualByComparingTo("125.00");
        assertThat(result.getJob().getTotalPrice()).isEqualByComparingTo("136.50");
        assertThat(result.getJob().getCourierSubprice()).isEqualByComparingTo("92.00");
        assertThat(result.getJob().getCourierPrice()).isEqualByComparingTo("100.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_SUBTOTAL)).isEqualByComparingTo("100.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_PROV)).isEqualByComparingTo("25.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_SERVICE_PROV)).isEqualByComparingTo("25.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_AGIO)).isEqualByComparingTo("5.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_SERVICEPRICE)).isEqualByComparingTo("8.00");
    }

    @Test
    void calculateDraftKeepsManualCourierFixpriceWhenStaleProvisionAndAgioExist() {
        Job job = new Job();
        job.setHeadquartersId(1L);
        job.setCostCenterPayerId(10L);
        job.setCourierId(77L);
        job.setOrderTime(ORDER_TIME);
        job.setFixPrice(new BigDecimal("100"));
        job.setServicePrice(new BigDecimal("10"));

        stubCustomer(BigDecimal.ZERO, BigDecimal.ZERO, 0);
        stubParameter(ParameterKeys.INV_SERVICEPRICE_DISCOUNT, 1L, "0");
        stubParameter(ParameterKeys.MASK_CR_PRICE_MODE, 1L, "1");
        stubParameter(ParameterKeys.MASK_CR_PRICE_MODE_DATE, 1L, "2023-04-01");

        ServicePriceHistory agioHistory = new ServicePriceHistory();
        agioHistory.setObjectType("cr");
        agioHistory.setObjectId(77L);
        agioHistory.setFieldName("cscr_prov");
        agioHistory.setContext("100");
        agioHistory.setPrice(new BigDecimal("5"));
        agioHistory.setValidFrom(ORDER_TIME.minusDays(1));
        when(servicePriceHistoryRepository.findLatestFieldValueWithContext("cr", 77L, "cscr_prov", "100", ORDER_TIME))
                .thenReturn(List.of(agioHistory));

        JobPricingResultDto result = jobPricingService.calculateDraft(
                job,
                List.of(
                        price(JobPricingService.JB_CR_FIXPRICE, "60"),
                        price(JobPricingService.JB_CR_MARKUP, "5"),
                        price(JobPricingService.JB_CR_PROV, "25")
                )
        );

        assertThat(result.getJob().getSubtotalPrice()).isEqualByComparingTo("110.00");
        assertThat(result.getJob().getTotalPrice()).isEqualByComparingTo("110.00");
        assertThat(result.getJob().getCourierSubprice()).isEqualByComparingTo("60.00");
        assertThat(result.getJob().getCourierPrice()).isEqualByComparingTo("70.50");
        assertThat(findPrice(result, JobPricingService.JB_CR_SUBTOTAL)).isEqualByComparingTo("67.50");
        assertThat(findPrice(result, JobPricingService.JB_CR_SERVICE_PROV)).isEqualByComparingTo("25.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_SERVICEPRICE)).isEqualByComparingTo("7.50");
        assertThat(hasPrice(result, JobPricingService.JB_CR_PROV)).isFalse();
        assertThat(hasPrice(result, JobPricingService.JB_CR_AGIO)).isFalse();
    }

    @Test
    void calculateDraftKeepsExplicitCourierServicePriceWhenAgioAdjustsBasePrice() {
        Job job = new Job();
        job.setHeadquartersId(1L);
        job.setCostCenterPayerId(10L);
        job.setCourierId(77L);
        job.setOrderTime(ORDER_TIME);
        job.setFixPrice(new BigDecimal("100"));
        job.setServicePrice(new BigDecimal("10"));

        stubCustomer(BigDecimal.ZERO, BigDecimal.ZERO, 0);
        stubParameter(ParameterKeys.INV_SERVICEPRICE_DISCOUNT, 1L, "0");
        stubParameter(ParameterKeys.MASK_CR_PRICE_MODE, 1L, "1");
        stubParameter(ParameterKeys.MASK_CR_PRICE_MODE_DATE, 1L, "2023-04-01");

        ServicePriceHistory agioHistory = new ServicePriceHistory();
        agioHistory.setObjectType("cr");
        agioHistory.setObjectId(77L);
        agioHistory.setFieldName("cscr_prov");
        agioHistory.setContext("100");
        agioHistory.setPrice(new BigDecimal("5"));
        agioHistory.setValidFrom(ORDER_TIME.minusDays(1));
        when(servicePriceHistoryRepository.findLatestFieldValueWithContext("cr", 77L, "cscr_prov", "100", ORDER_TIME))
                .thenReturn(List.of(agioHistory));

        JobPricingResultDto result = jobPricingService.calculateDraft(
                job,
                List.of(
                        price(JobPricingService.JB_CR_MARKUP, "5"),
                        price(JobPricingService.JB_CR_PROV, "25"),
                        price(JobPricingService.JB_CR_FIXSERVICEPRICE, "9"),
                        price(JobPricingService.JB_CR_SERVICE_PROV, "25")
                )
        );

        assertThat(result.getJob().getSubtotalPrice()).isEqualByComparingTo("110.00");
        assertThat(result.getJob().getTotalPrice()).isEqualByComparingTo("110.00");
        assertThat(result.getJob().getCourierSubprice()).isEqualByComparingTo("80.00");
        assertThat(result.getJob().getCourierPrice()).isEqualByComparingTo("93.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_SUBTOTAL)).isEqualByComparingTo("89.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_PROV)).isEqualByComparingTo("25.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_AGIO)).isEqualByComparingTo("5.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_FIXSERVICEPRICE)).isEqualByComparingTo("9.00");
        assertThat(hasPrice(result, JobPricingService.JB_CR_SERVICE_PROV)).isFalse();
        assertThat(hasPrice(result, JobPricingService.JB_CR_SERVICEPRICE)).isFalse();
    }

    @Test
    void calculateDraftRecalculatesDerivedCourierServicePriceMetaSortFourWhenAgioAdjustsRates() {
        Job job = new Job();
        job.setHeadquartersId(1L);
        job.setCostCenterPayerId(10L);
        job.setCourierId(77L);
        job.setOrderTime(ORDER_TIME);
        job.setFixPrice(new BigDecimal("100"));
        job.setServicePrice(new BigDecimal("10"));

        stubCustomer(BigDecimal.ZERO, BigDecimal.ZERO, 0);
        stubParameter(ParameterKeys.INV_SERVICEPRICE_DISCOUNT, 1L, "0");
        stubParameter(ParameterKeys.MASK_CR_PRICE_MODE, 1L, "1");
        stubParameter(ParameterKeys.MASK_CR_PRICE_MODE_DATE, 1L, "2023-04-01");

        ServicePriceHistory agioHistory = new ServicePriceHistory();
        agioHistory.setObjectType("cr");
        agioHistory.setObjectId(77L);
        agioHistory.setFieldName("cscr_prov");
        agioHistory.setContext("100");
        agioHistory.setPrice(new BigDecimal("5"));
        agioHistory.setValidFrom(ORDER_TIME.minusDays(1));
        when(servicePriceHistoryRepository.findLatestFieldValueWithContext("cr", 77L, "cscr_prov", "100", ORDER_TIME))
                .thenReturn(List.of(agioHistory));

        JobPricingResultDto result = jobPricingService.calculateDraft(
                job,
                List.of(
                        price(JobPricingService.JB_CR_MARKUP, "5"),
                        price(JobPricingService.JB_CR_PROV, "25"),
                        price(JobPricingService.JB_CR_SERVICEPRICE, "9"),
                        price(JobPricingService.JB_CR_SERVICE_PROV, "25")
                )
        );

        assertThat(result.getJob().getSubtotalPrice()).isEqualByComparingTo("110.00");
        assertThat(result.getJob().getTotalPrice()).isEqualByComparingTo("110.00");
        assertThat(result.getJob().getCourierSubprice()).isEqualByComparingTo("80.00");
        assertThat(result.getJob().getCourierPrice()).isEqualByComparingTo("92.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_SUBTOTAL)).isEqualByComparingTo("88.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_PROV)).isEqualByComparingTo("25.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_AGIO)).isEqualByComparingTo("5.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_SERVICEPRICE)).isEqualByComparingTo("8.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_SERVICE_PROV)).isEqualByComparingTo("25.00");
        assertThat(hasPrice(result, JobPricingService.JB_CR_FIXSERVICEPRICE)).isFalse();
    }

    @Test
    void calculateDraftIgnoresCourierAgioBeforeCrPriceModeDate() {
        Job job = new Job();
        job.setHeadquartersId(1L);
        job.setCostCenterPayerId(10L);
        job.setCourierId(77L);
        job.setOrderTime(ORDER_TIME);
        job.setVehicleTypeId(2);
        job.setServicePrice(new BigDecimal("10"));
        job.setMarkup(new BigDecimal("10"));

        stubCustomer(BigDecimal.ZERO, BigDecimal.ZERO, 1);
        stubParameter(ParameterKeys.INV_SERVICEPRICE_DISCOUNT, 1L, "0");
        stubParameter(ParameterKeys.MASK_CR_PRICE_MODE, 1L, "1");
        stubParameter(ParameterKeys.MASK_CR_PRICE_MODE_DATE, 1L, "2026-04-01");

        MetaType vehicleType = new MetaType();
        vehicleType.setSort(2);
        vehicleType.setType("vehicletype");
        vehicleType.setValue("PKW");
        when(metaTypeRepository.findBySortAndType(2, "vehicletype")).thenReturn(Optional.of(vehicleType));

        ServiceType serviceType = new ServiceType();
        serviceType.setId(501L);
        serviceType.setName("PKW");
        serviceType.setMode(0);
        serviceType.setHeadquartersId(1L);

        ServiceEntity basicService = new ServiceEntity();
        basicService.setId(601L);
        basicService.setName("Grundpreis");
        basicService.setMode(0);
        basicService.setServiceTypeId(501L);
        basicService.setHeadquartersId(1L);

        ServiceEntity globalCourierDiscount = new ServiceEntity();
        globalCourierDiscount.setId(602L);
        globalCourierDiscount.setName("Globaler<br>Frachtverg.-Abschlag [%]");
        globalCourierDiscount.setMode(0);
        globalCourierDiscount.setServiceTypeId(501L);
        globalCourierDiscount.setHeadquartersId(1L);

        when(serviceTypeRepository.findAvailableForCustomer(1L, 100L)).thenReturn(List.of(serviceType));
        when(serviceEntityRepository.findAvailableForCustomer(1L, 100L)).thenReturn(List.of(basicService, globalCourierDiscount));

        ServiceHistory basicHistory = new ServiceHistory();
        basicHistory.setServiceId(601L);
        basicHistory.setServiceTypeId(501L);
        basicHistory.setHeadquartersId(1L);
        basicHistory.setCustomerId(0L);
        basicHistory.setValidFrom(ORDER_TIME.minusDays(1));
        basicHistory.setPrice(new BigDecimal("20"));

        ServiceHistory globalDiscountHistory = new ServiceHistory();
        globalDiscountHistory.setServiceId(602L);
        globalDiscountHistory.setServiceTypeId(501L);
        globalDiscountHistory.setHeadquartersId(1L);
        globalDiscountHistory.setCustomerId(0L);
        globalDiscountHistory.setValidFrom(ORDER_TIME.minusDays(1));
        globalDiscountHistory.setCourierPrice(new BigDecimal("25"));

        when(serviceHistoryRepository.findLatestPriceForType(601L, 501L, 100L, 1L, ORDER_TIME)).thenReturn(List.of());
        when(serviceHistoryRepository.findLatestPriceForType(601L, 501L, 0L, 1L, ORDER_TIME)).thenReturn(List.of(basicHistory));
        when(serviceHistoryRepository.findLatestPriceForType(602L, 501L, 100L, 1L, ORDER_TIME)).thenReturn(List.of());
        when(serviceHistoryRepository.findLatestPriceForType(602L, 501L, 0L, 1L, ORDER_TIME)).thenReturn(List.of(globalDiscountHistory));

        ServicePriceHistory agioHistory = new ServicePriceHistory();
        agioHistory.setObjectType("cr");
        agioHistory.setObjectId(77L);
        agioHistory.setFieldName("cscr_prov");
        agioHistory.setContext("100");
        agioHistory.setPrice(new BigDecimal("5"));
        agioHistory.setValidFrom(ORDER_TIME.minusDays(1));
        when(servicePriceHistoryRepository.findLatestFieldValueWithContext("cr", 77L, "cscr_prov", "100", ORDER_TIME))
                .thenReturn(List.of(agioHistory));

        ServiceZipCode fromZipCode = new ServiceZipCode();
        fromZipCode.setId(1L);
        fromZipCode.setZipcode("20095");
        ServiceZipCode toZipCode = new ServiceZipCode();
        toZipCode.setId(2L);
        toZipCode.setZipcode("10115");
        when(serviceZipCodeRepository.findByZipcode("20095")).thenReturn(Optional.of(fromZipCode));
        when(serviceZipCodeRepository.findByZipcode("10115")).thenReturn(Optional.of(toZipCode));

        ServiceZipCodeHistory routeHistory = new ServiceZipCodeHistory();
        routeHistory.setFromZipCodeId(1L);
        routeHistory.setToZipCodeId(2L);
        routeHistory.setHeadquartersId(1L);
        routeHistory.setCustomerId(0L);
        routeHistory.setVehicleTypeId(2);
        routeHistory.setValidFrom(ORDER_TIME.minusDays(1));
        routeHistory.setPrice(new BigDecimal("100"));
        routeHistory.setDiscount(new BigDecimal("5"));

        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 100L, 1L, 2, ORDER_TIME)).thenReturn(List.of());
        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 100L, 1L, 0, ORDER_TIME)).thenReturn(List.of());
        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 0L, 1L, 2, ORDER_TIME)).thenReturn(List.of(routeHistory));

        TourDetailDto pickup = new TourDetailDto();
        pickup.setSort(1);
        pickup.setZipcode("20095");
        TourDetailDto delivery = new TourDetailDto();
        delivery.setSort(2);
        delivery.setZipcode("10115");

        JobPricingResultDto result = jobPricingService.calculateDraft(job, List.of(), List.of(pickup, delivery));

        assertThat(result.getJob().getSubtotalPrice()).isEqualByComparingTo("125.00");
        assertThat(result.getJob().getTotalPrice()).isEqualByComparingTo("136.50");
        assertThat(result.getJob().getCourierSubprice()).isEqualByComparingTo("86.25");
        assertThat(result.getJob().getCourierPrice()).isEqualByComparingTo("86.25");
        assertThat(findPrice(result, JobPricingService.JB_CR_SUBTOTAL)).isEqualByComparingTo("86.25");
        assertThat(findPrice(result, JobPricingService.JB_CR_PROV)).isEqualByComparingTo("25.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_SERVICE_PROV)).isEqualByComparingTo("25.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_AGIO)).isEqualByComparingTo("0.00");
        assertThat(findPrice(result, JobPricingService.JB_CR_SERVICEPRICE)).isEqualByComparingTo("7.50");
    }

    @Test
    void calculateDraftUsesHistoricalCustomerFuelMarkupProvision() {
        Job job = new Job();
        job.setHeadquartersId(1L);
        job.setCostCenterPayerId(10L);
        job.setOrderTime(ORDER_TIME);
        job.setVehicleTypeId(2);

        stubCustomer(BigDecimal.ZERO, BigDecimal.ZERO, 1, 1, new BigDecimal("12"));
        stubParameter(ParameterKeys.INV_SERVICEPRICE_DISCOUNT, 1L, "0");

        MetaType vehicleType = new MetaType();
        vehicleType.setSort(2);
        vehicleType.setType("vehicletype");
        vehicleType.setValue("PKW");
        when(metaTypeRepository.findBySortAndType(2, "vehicletype")).thenReturn(Optional.of(vehicleType));

        ServiceType serviceType = new ServiceType();
        serviceType.setId(501L);
        serviceType.setName("PKW");
        serviceType.setMode(0);
        serviceType.setHeadquartersId(1L);

        ServiceEntity basicService = new ServiceEntity();
        basicService.setId(601L);
        basicService.setName("Grundpreis");
        basicService.setMode(0);
        basicService.setServiceTypeId(501L);
        basicService.setHeadquartersId(1L);

        ServiceEntity globalCourierDiscount = new ServiceEntity();
        globalCourierDiscount.setId(602L);
        globalCourierDiscount.setName("Globaler<br>Frachtverg.-Abschlag [%]");
        globalCourierDiscount.setMode(0);
        globalCourierDiscount.setServiceTypeId(501L);
        globalCourierDiscount.setHeadquartersId(1L);

        when(serviceTypeRepository.findAvailableForCustomer(1L, 100L)).thenReturn(List.of(serviceType));
        when(serviceEntityRepository.findAvailableForCustomer(1L, 100L)).thenReturn(List.of(basicService, globalCourierDiscount));

        ServiceHistory basicHistory = new ServiceHistory();
        basicHistory.setServiceId(601L);
        basicHistory.setServiceTypeId(501L);
        basicHistory.setHeadquartersId(1L);
        basicHistory.setCustomerId(0L);
        basicHistory.setValidFrom(ORDER_TIME.minusDays(1));
        basicHistory.setPrice(new BigDecimal("20"));

        ServiceHistory globalDiscountHistory = new ServiceHistory();
        globalDiscountHistory.setServiceId(602L);
        globalDiscountHistory.setServiceTypeId(501L);
        globalDiscountHistory.setHeadquartersId(1L);
        globalDiscountHistory.setCustomerId(0L);
        globalDiscountHistory.setValidFrom(ORDER_TIME.minusDays(1));
        globalDiscountHistory.setCourierPrice(new BigDecimal("25"));

        when(serviceHistoryRepository.findLatestPriceForType(601L, 501L, 100L, 1L, ORDER_TIME)).thenReturn(List.of());
        when(serviceHistoryRepository.findLatestPriceForType(601L, 501L, 0L, 1L, ORDER_TIME)).thenReturn(List.of(basicHistory));
        when(serviceHistoryRepository.findLatestPriceForType(602L, 501L, 100L, 1L, ORDER_TIME)).thenReturn(List.of());
        when(serviceHistoryRepository.findLatestPriceForType(602L, 501L, 0L, 1L, ORDER_TIME)).thenReturn(List.of(globalDiscountHistory));

        ServicePriceHistory markupHistory = new ServicePriceHistory();
        markupHistory.setObjectType("cs");
        markupHistory.setObjectId(100L);
        markupHistory.setFieldName("cs_markup_prov");
        markupHistory.setPrice(new BigDecimal("15"));
        markupHistory.setValidFrom(ORDER_TIME.minusDays(1));
        when(servicePriceHistoryRepository.findLatestFieldValue("cs", 100L, "cs_markup_prov", ORDER_TIME))
                .thenReturn(List.of(markupHistory));

        ServiceZipCode fromZipCode = new ServiceZipCode();
        fromZipCode.setId(1L);
        fromZipCode.setZipcode("20095");
        ServiceZipCode toZipCode = new ServiceZipCode();
        toZipCode.setId(2L);
        toZipCode.setZipcode("10115");
        when(serviceZipCodeRepository.findByZipcode("20095")).thenReturn(Optional.of(fromZipCode));
        when(serviceZipCodeRepository.findByZipcode("10115")).thenReturn(Optional.of(toZipCode));

        ServiceZipCodeHistory routeHistory = new ServiceZipCodeHistory();
        routeHistory.setFromZipCodeId(1L);
        routeHistory.setToZipCodeId(2L);
        routeHistory.setHeadquartersId(1L);
        routeHistory.setCustomerId(0L);
        routeHistory.setVehicleTypeId(2);
        routeHistory.setValidFrom(ORDER_TIME.minusDays(1));
        routeHistory.setPrice(new BigDecimal("100"));

        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 100L, 1L, 2, ORDER_TIME)).thenReturn(List.of());
        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 0L, 1L, 2, ORDER_TIME)).thenReturn(List.of(routeHistory));

        TourDetailDto pickup = new TourDetailDto();
        pickup.setSort(1);
        pickup.setZipcode("20095");
        TourDetailDto delivery = new TourDetailDto();
        delivery.setSort(2);
        delivery.setZipcode("10115");

        JobPricingResultDto result = jobPricingService.calculateDraft(job, List.of(), List.of(pickup, delivery));

        assertThat(result.getJob().getMarkup()).isEqualByComparingTo("15.00");
        assertThat(result.getJob().getSubtotalPrice()).isEqualByComparingTo("120.00");
        assertThat(result.getJob().getTotalPrice()).isEqualByComparingTo("138.00");
        assertThat(result.getJob().getCourierSubprice()).isEqualByComparingTo("90.00");
        assertThat(result.getJob().getCourierPrice()).isEqualByComparingTo("103.50");
        assertThat(findPrice(result, JobPricingService.JB_CR_PROV)).isEqualByComparingTo("25.00");
    }

    @Test
    void calculateDraftSkipsFuelMarkupOnBasePriceWhenConfigured() {
        Job job = new Job();
        job.setHeadquartersId(1L);
        job.setCostCenterPayerId(10L);
        job.setOrderTime(ORDER_TIME);
        job.setVehicleTypeId(2);
        job.setMarkup(new BigDecimal("10"));

        stubCustomer(BigDecimal.ZERO, BigDecimal.ZERO, 1);
        stubParameter(ParameterKeys.INV_SERVICEPRICE_DISCOUNT, 1L, "0");
        stubParameter(ParameterKeys.MASK_NO_MARKUP_ON_VEHICLE_PRICE, 1L, "1");

        JobPrice courierMarkup = price(JobPricingService.JB_CR_MARKUP, "5");

        MetaType vehicleType = new MetaType();
        vehicleType.setSort(2);
        vehicleType.setType("vehicletype");
        vehicleType.setValue("PKW");
        when(metaTypeRepository.findBySortAndType(2, "vehicletype")).thenReturn(Optional.of(vehicleType));

        ServiceType serviceType = new ServiceType();
        serviceType.setId(501L);
        serviceType.setName("PKW");
        serviceType.setMode(0);
        serviceType.setHeadquartersId(1L);

        ServiceEntity basicService = new ServiceEntity();
        basicService.setId(601L);
        basicService.setName("Grundpreis");
        basicService.setMode(0);
        basicService.setServiceTypeId(501L);
        basicService.setHeadquartersId(1L);

        ServiceEntity globalCourierDiscount = new ServiceEntity();
        globalCourierDiscount.setId(602L);
        globalCourierDiscount.setName("Globaler<br>Frachtverg.-Abschlag [%]");
        globalCourierDiscount.setMode(0);
        globalCourierDiscount.setServiceTypeId(501L);
        globalCourierDiscount.setHeadquartersId(1L);

        when(serviceTypeRepository.findAvailableForCustomer(1L, 100L)).thenReturn(List.of(serviceType));
        when(serviceEntityRepository.findAvailableForCustomer(1L, 100L)).thenReturn(List.of(basicService, globalCourierDiscount));

        ServiceHistory basicHistory = new ServiceHistory();
        basicHistory.setServiceId(601L);
        basicHistory.setServiceTypeId(501L);
        basicHistory.setHeadquartersId(1L);
        basicHistory.setCustomerId(0L);
        basicHistory.setValidFrom(ORDER_TIME.minusDays(1));
        basicHistory.setPrice(new BigDecimal("20"));

        ServiceHistory globalDiscountHistory = new ServiceHistory();
        globalDiscountHistory.setServiceId(602L);
        globalDiscountHistory.setServiceTypeId(501L);
        globalDiscountHistory.setHeadquartersId(1L);
        globalDiscountHistory.setCustomerId(0L);
        globalDiscountHistory.setValidFrom(ORDER_TIME.minusDays(1));
        globalDiscountHistory.setCourierPrice(new BigDecimal("25"));

        when(serviceHistoryRepository.findLatestPriceForType(601L, 501L, 100L, 1L, ORDER_TIME)).thenReturn(List.of());
        when(serviceHistoryRepository.findLatestPriceForType(601L, 501L, 0L, 1L, ORDER_TIME)).thenReturn(List.of(basicHistory));
        when(serviceHistoryRepository.findLatestPriceForType(602L, 501L, 100L, 1L, ORDER_TIME)).thenReturn(List.of());
        when(serviceHistoryRepository.findLatestPriceForType(602L, 501L, 0L, 1L, ORDER_TIME)).thenReturn(List.of(globalDiscountHistory));

        ServiceZipCode fromZipCode = new ServiceZipCode();
        fromZipCode.setId(1L);
        fromZipCode.setZipcode("20095");
        ServiceZipCode toZipCode = new ServiceZipCode();
        toZipCode.setId(2L);
        toZipCode.setZipcode("10115");
        when(serviceZipCodeRepository.findByZipcode("20095")).thenReturn(Optional.of(fromZipCode));
        when(serviceZipCodeRepository.findByZipcode("10115")).thenReturn(Optional.of(toZipCode));

        ServiceZipCodeHistory routeHistory = new ServiceZipCodeHistory();
        routeHistory.setFromZipCodeId(1L);
        routeHistory.setToZipCodeId(2L);
        routeHistory.setHeadquartersId(1L);
        routeHistory.setCustomerId(0L);
        routeHistory.setVehicleTypeId(2);
        routeHistory.setValidFrom(ORDER_TIME.minusDays(1));
        routeHistory.setPrice(new BigDecimal("100"));

        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 100L, 1L, 2, ORDER_TIME)).thenReturn(List.of());
        when(serviceZipCodeHistoryRepository.findLatest(1L, 2L, 0L, 1L, 2, ORDER_TIME)).thenReturn(List.of(routeHistory));

        TourDetailDto pickup = new TourDetailDto();
        pickup.setSort(1);
        pickup.setZipcode("20095");
        TourDetailDto delivery = new TourDetailDto();
        delivery.setSort(2);
        delivery.setZipcode("10115");

        JobPricingResultDto result = jobPricingService.calculateDraft(job, List.of(courierMarkup), List.of(pickup, delivery));

        assertThat(result.getJob().getSubtotalPrice()).isEqualByComparingTo("120.00");
        assertThat(result.getJob().getTotalPrice()).isEqualByComparingTo("130.00");
        assertThat(result.getJob().getCourierSubprice()).isEqualByComparingTo("90.00");
        assertThat(result.getJob().getCourierPrice()).isEqualByComparingTo("93.75");
        assertThat(findPrice(result, JobPricingService.JB_CR_PROV)).isEqualByComparingTo("25.00");
    }

    private void stubCustomer(BigDecimal discount, BigDecimal fixpriceDiscount, Integer invoiceMode) {
        stubCustomer(discount, fixpriceDiscount, invoiceMode, null, null);
    }

    private void stubCustomer(BigDecimal discount,
                              BigDecimal fixpriceDiscount,
                              Integer invoiceMode,
                              Integer fuelMarkupEnabled,
                              BigDecimal fuelMarkupProvision) {
        CostCenter costCenter = new CostCenter();
        costCenter.setId(10L);
        costCenter.setCustomerId(100L);
        when(costCenterRepository.findById(10L)).thenReturn(Optional.of(costCenter));

        Customer customer = new Customer();
        customer.setId(100L);
        customer.setHeadquartersId(1L);
        customer.setDiscount(discount);
        customer.setFixpriceDiscount(fixpriceDiscount);
        customer.setInvoiceMode(invoiceMode);
        customer.setFuelMarkupEnabled(fuelMarkupEnabled);
        customer.setFuelMarkupProvision(fuelMarkupProvision);
        when(customerRepository.findById(100L)).thenReturn(Optional.of(customer));
    }

    private JobPrice price(int sort, String value) {
        JobPrice price = new JobPrice();
        price.setMetaSort(sort);
        price.setPrice(new BigDecimal(value));
        return price;
    }

    private void stubParameter(String key, Long hqId, String value) {
        Parameter parameter = new Parameter();
        parameter.setKey(key);
        parameter.setHeadquartersId(hqId);
        parameter.setEmployeeId(0L);
        parameter.setValue(value);
        when(parameterRepository.findByKeyAndHqAndEmployee(key, hqId, 0L)).thenReturn(Optional.of(parameter));
        if (!Long.valueOf(0L).equals(hqId)) {
            when(parameterRepository.findByKeyAndHqAndEmployee(key, 0L, 0L)).thenReturn(Optional.empty());
        }
    }

    private BigDecimal findPrice(JobPricingResultDto result, int metaSort) {
        return result.getPrices().stream()
                .filter(row -> Integer.valueOf(metaSort).equals(row.getMetaSort()))
                .map(JobPrice::getPrice)
                .findFirst()
                .orElse(BigDecimal.ZERO);
    }

    private boolean hasPrice(JobPricingResultDto result, int metaSort) {
        return result.getPrices().stream()
                .anyMatch(row -> Integer.valueOf(metaSort).equals(row.getMetaSort()));
    }
}
