Refactor Report DTOs: extracted ReportPremisesDTO, adjusted ReportDestinationDTO, updated frontend to align with new structure, and enhanced reporting logic.
This commit is contained in:
parent
3aa86b4eea
commit
175ac4266b
12 changed files with 437 additions and 234 deletions
|
|
@ -133,9 +133,12 @@ export default {
|
|||
.box-content.collapsed {
|
||||
max-height: 0;
|
||||
opacity: 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
|
||||
margin: 0 !important; /* ← !important um alle margins zu überschreiben */
|
||||
padding: 0 !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
||||
|
|
@ -7,42 +7,53 @@
|
|||
<report-chart
|
||||
title=""
|
||||
:mek_a="report.costs.mek_a.total"
|
||||
:logistics_costs="report.risk.mek_b.total-report.costs.mek_a.total"
|
||||
:chance_cost="report.risk.opportunity_scenario.total"
|
||||
:risk_cost="report.risk.risk_scenario.total"
|
||||
:logistics_costs="report.overview.mek_b.total-report.costs.mek_a.total"
|
||||
:chance_cost="report.overview.opportunity_scenario.total"
|
||||
:risk_cost="report.overview.risk_scenario.total"
|
||||
:scale="chartScale"
|
||||
></report-chart>
|
||||
</div>
|
||||
|
||||
<!-- summary -->
|
||||
<div class="box-gap">
|
||||
<collapsible-box :is-collapsable="false" variant="border" title="Overview" size="m" :stretch-content="true">
|
||||
<collapsible-box :is-collapsable="false" variant="border" title="Summary" size="m" :stretch-content="true">
|
||||
<div class="report-content-container--3-col">
|
||||
|
||||
<div class="report-content-row">
|
||||
<div class="report-content-row-highlight">MEK B</div>
|
||||
<div class="report-content-data-cell report-content-row-highlight">{{ report.risk.mek_b.total.toFixed(2) }} €</div>
|
||||
<div class="report-content-data-cell"></div>
|
||||
</div>
|
||||
|
||||
<div class="report-content-row">
|
||||
<div>Opportunity scenario</div>
|
||||
<div class="report-content-data-cell">{{ report.risk.opportunity_scenario.total.toFixed(2) }} €</div>
|
||||
<div>MEK A</div>
|
||||
<div class="report-content-data-cell">{{ report.overview.mek_a.total.toFixed(2) }} €</div>
|
||||
<div class="report-content-data-cell">{{
|
||||
`${(report.risk.opportunity_scenario.percentage * 100).toFixed(2)} %`
|
||||
`${(report.overview.mek_a.percentage * 100).toFixed(2)} %`
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="report-content-row">
|
||||
<div>Risk scenario</div>
|
||||
<div class="report-content-data-cell">{{ report.risk.risk_scenario.total.toFixed(2) }} €</div>
|
||||
<div class="report-content-data-cell">{{ `${(report.risk.risk_scenario.percentage * 100).toFixed(2)} %`}}</div>
|
||||
<div>Logistics cost</div>
|
||||
<div class="report-content-data-cell">{{ report.overview.logistics.total.toFixed(2) }} €</div>
|
||||
<div class="report-content-data-cell">{{
|
||||
`${(report.overview.logistics.percentage * 100).toFixed(2)} %`
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="report-content-row">
|
||||
<div class="report-content-row-highlight">MEK B</div>
|
||||
<div class="report-content-data-cell report-content-row-highlight">{{
|
||||
report.overview.mek_b.total.toFixed(2)
|
||||
}} €
|
||||
</div>
|
||||
<div class="report-content-data-cell report-content-row-highlight">{{
|
||||
`${(report.overview.mek_b.percentage * 100).toFixed(2)} %`
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</collapsible-box>
|
||||
</div>
|
||||
<!-- weighted cost breakdown-->
|
||||
<div class="box-gap">
|
||||
<collapsible-box :is-collapsable="false" variant="border" title="Weighted cost breakdown" size="m"
|
||||
:stretch-content="true">
|
||||
|
|
@ -142,18 +153,129 @@
|
|||
<div class="report-content-data-cell">{{ (report.costs.capital.percentage * 100).toFixed(2) }}</div>
|
||||
</div>
|
||||
|
||||
<div class="report-content-row">
|
||||
<div class="report-content-row-highlight">Total</div>
|
||||
<div class="report-content-data-cell report-content-row-highlight">{{
|
||||
report.costs.total.total.toFixed(2)
|
||||
}}
|
||||
</div>
|
||||
<div class="report-content-data-cell report-content-row-highlight">
|
||||
{{ (report.costs.total.percentage * 100).toFixed(2) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</collapsible-box>
|
||||
</div>
|
||||
|
||||
<!-- all time high/low container rate-->
|
||||
<div class="box-gap">
|
||||
<collapsible-box :is-collapsable="true" variant="border" title="All time high/low MEK_B"
|
||||
:initially-collapsed="true"
|
||||
:stretch-content="true" size="m">
|
||||
|
||||
<div class="box-gap" :key="premise.id" v-for="(premise, idx) in report.premises">
|
||||
<div class="report-content-container--3-col-2">
|
||||
|
||||
<collapsible-box class="report-content-container" variant="border" :title="premise.destination.name"
|
||||
<div class="report-content-row">
|
||||
<div></div>
|
||||
<div class="report-content-data-header-cell">total [€]</div>
|
||||
<div class="report-content-data-header-cell">of MEK_B [%]</div>
|
||||
</div>
|
||||
|
||||
<div class="report-content-row">
|
||||
<div>Opportunity scenario</div>
|
||||
<div class="report-content-data-cell">{{ report.overview.opportunity_scenario.total.toFixed(2) }} €</div>
|
||||
<div class="report-content-data-cell">{{
|
||||
`${(report.overview.opportunity_scenario.percentage * 100).toFixed(2)}`
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="report-content-row">
|
||||
<div>Risk scenario</div>
|
||||
<div class="report-content-data-cell">{{ report.overview.risk_scenario.total.toFixed(2) }} €</div>
|
||||
<div class="report-content-data-cell">
|
||||
{{ `${(report.overview.risk_scenario.percentage * 100).toFixed(2)}` }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</collapsible-box>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- material and handling unit-->
|
||||
<div class="box-gap">
|
||||
<collapsible-box :is-collapsable="false" variant="border" title="Material" size="m"
|
||||
:stretch-content="true">
|
||||
|
||||
<div class="report-content-container--2-col">
|
||||
|
||||
<div class="report-content-row">
|
||||
<div>Part number</div>
|
||||
<div class="report-content-data-cell"> {{ report.material.part_number }}</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="report-content-row">
|
||||
<div>HS code</div>
|
||||
<div class="report-content-data-cell"> {{ report.premises.hs_code }}</div>
|
||||
</div>
|
||||
|
||||
<div class="report-content-row">
|
||||
<div>Tariff rate</div>
|
||||
<div class="report-content-data-cell"> {{ (report.premises.tariff_rate * 100).toFixed(2) }}%</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="report-sub-header">Handling unit</div>
|
||||
|
||||
<div class="report-content-container--2-col">
|
||||
|
||||
<div class="report-content-row">
|
||||
<div>Dimensions [{{ report.premises.dimension_unit }}]</div>
|
||||
<div class="report-content-data-cell">{{
|
||||
toFixedDimension(report.premises.length, report.premises.dimension_unit)
|
||||
}} x
|
||||
{{ toFixedDimension(report.premises.width, report.premises.dimension_unit) }} x
|
||||
{{ toFixedDimension(report.premises.height, report.premises.dimension_unit) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="report-content-row">
|
||||
<div>Weight [{{ report.premises.weight_unit }}]</div>
|
||||
<div class="report-content-data-cell">{{
|
||||
toFixedWeight(report.premises.weight, report.premises.weight_unit)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="report-content-row">
|
||||
<div>Unit count</div>
|
||||
<div class="report-content-data-cell">{{ report.premises.hu_unit_count }}</div>
|
||||
</div>
|
||||
|
||||
<div class="report-content-row">
|
||||
<div>Mixed transport</div>
|
||||
<div class="report-content-data-cell">{{ report.premises.mixed ? 'Yes' : 'No' }}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</collapsible-box>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- destinations -->
|
||||
<div class="box-gap" :key="destination.id" v-for="(destination, idx) in report.destinations">
|
||||
|
||||
<collapsible-box class="report-content-container" variant="border" :title="destination.destination.name"
|
||||
:stretch-content="true" :initially-collapsed="true">
|
||||
<div>
|
||||
<report-route :sections="premise.sections" :destination="premise.destination"
|
||||
<report-route :sections="destination.sections" :destination="destination.destination"
|
||||
:route-section-scale="routeSectionScale[idx]"></report-route>
|
||||
|
||||
<div class="report-sub-header">General</div>
|
||||
|
|
@ -162,69 +284,31 @@
|
|||
|
||||
<div class="report-content-row">
|
||||
<div>Annual Quantity</div>
|
||||
<div class="report-content-data-cell">{{ premise.annual_quantity }}</div>
|
||||
</div>
|
||||
|
||||
<div class="report-content-row">
|
||||
<div>HS code</div>
|
||||
<div class="report-content-data-cell">{{ premise.hs_code }}</div>
|
||||
</div>
|
||||
|
||||
<div class="report-content-row">
|
||||
<div>Tariff rate</div>
|
||||
<div class="report-content-data-cell">{{ (premise.tariff_rate * 100).toFixed(2) }}%</div>
|
||||
<div class="report-content-data-cell">{{ destination.annual_quantity }}</div>
|
||||
</div>
|
||||
|
||||
<div class="report-content-row">
|
||||
<div>Oversea share</div>
|
||||
<div class="report-content-data-cell">{{ (premise.oversea_share * 100).toFixed(2) }}%</div>
|
||||
<div class="report-content-data-cell">{{ (destination.oversea_share * 100).toFixed(2) }}%</div>
|
||||
</div>
|
||||
|
||||
<div class="report-content-row" v-if="(premise.air_freight_share ?? null) !== null">
|
||||
<div class="report-content-row" v-if="(destination.air_freight_share ?? null) !== null">
|
||||
<div>Airfreight share</div>
|
||||
<div class="report-content-data-cell">{{ (premise.air_freight_share * 100).toFixed(2) }}%</div>
|
||||
<div class="report-content-data-cell">{{ (destination.air_freight_share * 100).toFixed(2) }}%</div>
|
||||
</div>
|
||||
|
||||
<div class="report-content-row">
|
||||
<div>Transit time [days]</div>
|
||||
<div class="report-content-data-cell">{{ premise.transport_time }}</div>
|
||||
<div class="report-content-data-cell">{{ destination.transport_time }}</div>
|
||||
</div>
|
||||
|
||||
<div class="report-content-row">
|
||||
<div>Safety stock [w-days]</div>
|
||||
<div class="report-content-data-cell">{{ premise.safety_stock }}</div>
|
||||
<div class="report-content-data-cell">{{ destination.safety_stock }}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="report-sub-header">Handling unit</div>
|
||||
|
||||
<div class="report-content-container--2-col">
|
||||
|
||||
<div class="report-content-row">
|
||||
<div>Dimensions [{{ premise.dimension_unit }}]</div>
|
||||
<div class="report-content-data-cell">{{ toFixedDimension(premise.length, premise.dimension_unit) }} x
|
||||
{{ toFixedDimension(premise.width, premise.dimension_unit) }} x
|
||||
{{ toFixedDimension(premise.height, premise.dimension_unit) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="report-content-row">
|
||||
<div>Weight [{{ premise.weight_unit }}]</div>
|
||||
<div class="report-content-data-cell">{{ toFixedWeight(premise.weight, premise.weight_unit) }}</div>
|
||||
</div>
|
||||
|
||||
<div class="report-content-row">
|
||||
<div>Unit count</div>
|
||||
<div class="report-content-data-cell">{{ premise.hu_unit_count }}</div>
|
||||
</div>
|
||||
|
||||
<div class="report-content-row">
|
||||
<div>Mixed transport</div>
|
||||
<div class="report-content-data-cell">{{ premise.mixed ? 'Yes' : 'No' }}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="report-sub-header">Container</div>
|
||||
|
||||
|
|
@ -232,29 +316,31 @@
|
|||
|
||||
<div class="report-content-row">
|
||||
<div>Stacked layers</div>
|
||||
<div class="report-content-data-cell">{{ hasMainRun(premise.sections) ? premise.layer : '-' }}</div>
|
||||
<div class="report-content-data-cell">{{
|
||||
hasMainRun(destination.sections) ? destination.layer : '-'
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="report-content-row">
|
||||
<div>Container unit count</div>
|
||||
<div class="report-content-data-cell">
|
||||
{{ hasMainRun(premise.sections) ? (premise.unit_count * premise.hu_unit_count) : '-' }}
|
||||
{{ hasMainRun(destination.sections) ? (destination.unit_count * report.premises.hu_unit_count) : '-' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="report-content-row">
|
||||
<div>Container type</div>
|
||||
<div class="report-content-data-cell">
|
||||
{{ hasMainRun(premise.sections) ? getContainerTypeName(premise.container_type) : '-' }}
|
||||
{{ hasMainRun(destination.sections) ? getContainerTypeName(destination.container_type) : '-' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="report-content-row">
|
||||
<div>Limiting factor</div>
|
||||
<div class="report-content-data-cell">
|
||||
{{ hasMainRun(premise.sections) ? premise.weight_exceeded ? 'Weight' : 'Volume' : '-' }}
|
||||
{{ hasMainRun(destination.sections) ? destination.weight_exceeded ? 'Weight' : 'Volume' : '-' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -390,6 +476,14 @@ export default {
|
|||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.report-content-container--3-col-2 {
|
||||
display: grid;
|
||||
grid-template-columns: 5fr 3fr 3fr;
|
||||
gap: 1rem;
|
||||
margin-top: 1.6rem;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.report-content-row {
|
||||
display: contents;
|
||||
color: #6B869C;
|
||||
|
|
|
|||
|
|
@ -75,12 +75,12 @@ export default {
|
|||
},
|
||||
routeSectionScale() {
|
||||
const reports = this.reportsStore.reports;
|
||||
const scale = new Array(reports.map(r => r.premises.length).reduce((max, n) => Math.max(n, max), 0)).fill(0);
|
||||
const scale = new Array(reports.map(r => r.destinations.length).reduce((max, n) => Math.max(n, max), 0)).fill(0);
|
||||
|
||||
for (let i = 0; i < scale.length; i++) {
|
||||
for (const report of reports) {
|
||||
if(report.premises.length > i) {
|
||||
scale[i] = Math.max(scale[i], report.premises[i].sections.length);
|
||||
if(report.destinations.length > i) {
|
||||
scale[i] = Math.max(scale[i], report.destinations[i].sections.length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ export const useReportsStore = defineStore('reports', {
|
|||
let max = 0;
|
||||
|
||||
state.reports.forEach(report => {
|
||||
max = Math.max(report.risk.mek_b.total, max);
|
||||
max = Math.max(report.risk.risk_scenario.total, max);
|
||||
max = Math.max(report.overview.mek_b.total, max);
|
||||
max = Math.max(report.overview.risk_scenario.total, max);
|
||||
})
|
||||
|
||||
const magnitude = Math.pow(10, Math.floor(Math.log10(max)));
|
||||
|
|
@ -74,14 +74,14 @@ export const useReportsStore = defineStore('reports', {
|
|||
this.showComparableWarning = false;
|
||||
for (const [idx, report] of this.reports.entries()) {
|
||||
for (const otherReport of this.reports.slice(idx + 1)) {
|
||||
if (report.premises.length !== otherReport.premises.length) {
|
||||
if (report.destinations.length !== otherReport.destinations.length) {
|
||||
this.showComparableWarning = true;
|
||||
break;
|
||||
}
|
||||
|
||||
for (const premise of report.premises) {
|
||||
for (const premise of report.destinations) {
|
||||
|
||||
const otherPremise = otherReport.premises.find(otherPremise => otherPremise.destination.external_mapping_id === premise.destination.external_mapping_id);
|
||||
const otherPremise = otherReport.destinations.find(otherPremise => otherPremise.destination.external_mapping_id === premise.destination.external_mapping_id);
|
||||
|
||||
if((otherPremise ?? null) == null) {
|
||||
this.showComparableWarning = true;
|
||||
|
|
|
|||
|
|
@ -2,18 +2,17 @@ package de.avatic.lcc.controller.report;
|
|||
|
||||
import de.avatic.lcc.dto.generic.NodeDTO;
|
||||
import de.avatic.lcc.dto.report.ReportDTO;
|
||||
import de.avatic.lcc.dto.report.ReportSearchRequestDTO;
|
||||
import de.avatic.lcc.service.report.ExcelReportingService;
|
||||
import de.avatic.lcc.service.report.ReportingService;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import org.apache.coyote.Response;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -25,7 +24,6 @@ import java.util.List;
|
|||
@RequestMapping("/api/reports")
|
||||
public class ReportingController {
|
||||
|
||||
//TODO: rollenbeschränkung
|
||||
|
||||
private final ReportingService reportingService;
|
||||
private final ExcelReportingService excelReportingService;
|
||||
|
|
@ -42,6 +40,17 @@ public class ReportingController {
|
|||
this.excelReportingService = excelReportingService;
|
||||
}
|
||||
|
||||
@GetMapping({"/", ""})
|
||||
@PreAuthorize("hasAnyRole('SUPER', 'CALCULATION', 'BASIC')")
|
||||
public ResponseEntity<String> listReports(@RequestBody(required = false) ReportSearchRequestDTO filter,
|
||||
@RequestParam(defaultValue = "20") @Min(1) int limit,
|
||||
@RequestParam(defaultValue = "1") @Min(1) int page) {
|
||||
|
||||
//TODO implement me
|
||||
|
||||
return ResponseEntity.ok().build();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -23,10 +23,13 @@ public class ReportDTO {
|
|||
@JsonProperty("costs")
|
||||
public Map<String, ReportEntryDTO> cost;
|
||||
|
||||
@JsonProperty("risk")
|
||||
public Map<String, ReportEntryDTO> risk;
|
||||
@JsonProperty("overview")
|
||||
public Map<String, ReportEntryDTO> overview;
|
||||
|
||||
@JsonProperty("premises")
|
||||
private ReportPremisesDTO premises;
|
||||
|
||||
@JsonProperty("destinations")
|
||||
public List<ReportDestinationDTO> destinations;
|
||||
|
||||
public NodeDTO getSupplier() {
|
||||
|
|
@ -49,12 +52,12 @@ public class ReportDTO {
|
|||
this.cost = cost;
|
||||
}
|
||||
|
||||
public Map<String, ReportEntryDTO> getRisk() {
|
||||
return risk;
|
||||
public Map<String, ReportEntryDTO> getOverview() {
|
||||
return overview;
|
||||
}
|
||||
|
||||
public void setRisk(Map<String, ReportEntryDTO> risk) {
|
||||
this.risk = risk;
|
||||
public void setOverview(Map<String, ReportEntryDTO> overview) {
|
||||
this.overview = overview;
|
||||
}
|
||||
|
||||
public List<ReportDestinationDTO> getDestinations() {
|
||||
|
|
@ -88,4 +91,14 @@ public class ReportDTO {
|
|||
public void setEndDate(LocalDateTime endDate) {
|
||||
this.endDate = endDate;
|
||||
}
|
||||
|
||||
|
||||
public ReportPremisesDTO getPremises() {
|
||||
return premises;
|
||||
}
|
||||
|
||||
public void setPremises(ReportPremisesDTO premises) {
|
||||
this.premises = premises;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,16 +16,6 @@ public class ReportDestinationDTO {
|
|||
private List<ReportSectionDTO> sections;
|
||||
|
||||
/* general */
|
||||
|
||||
@JsonProperty("annual_quantity")
|
||||
private Integer annualQuantity;
|
||||
|
||||
@JsonProperty("hs_code")
|
||||
private String hsCode;
|
||||
|
||||
@JsonProperty("tariff_rate")
|
||||
private Number tariffRate;
|
||||
|
||||
@JsonProperty("oversea_share")
|
||||
private Double overseaShare;
|
||||
|
||||
|
|
@ -38,29 +28,12 @@ public class ReportDestinationDTO {
|
|||
@JsonProperty("safety_stock")
|
||||
private Integer safetyStock;
|
||||
|
||||
/* packaging */
|
||||
|
||||
private Double width;
|
||||
|
||||
private Double height;
|
||||
|
||||
private Double length;
|
||||
|
||||
private Double weight;
|
||||
|
||||
@JsonProperty("dimension_unit")
|
||||
private DimensionUnit dimensionUnit;
|
||||
|
||||
@JsonProperty("weight_unit")
|
||||
private WeightUnit weightUnit;
|
||||
|
||||
@JsonProperty("hu_unit_count")
|
||||
private Integer huUnitCount;
|
||||
@JsonProperty("annual_quantity")
|
||||
private Integer annualQuantity;
|
||||
|
||||
private Integer layer;
|
||||
|
||||
/* container */
|
||||
|
||||
@JsonProperty("unit_count")
|
||||
private Number unitCount;
|
||||
|
||||
|
|
@ -77,7 +50,6 @@ public class ReportDestinationDTO {
|
|||
|
||||
private Boolean mixed;
|
||||
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
|
@ -110,22 +82,6 @@ public class ReportDestinationDTO {
|
|||
this.annualQuantity = annualQuantity;
|
||||
}
|
||||
|
||||
public String getHsCode() {
|
||||
return hsCode;
|
||||
}
|
||||
|
||||
public void setHsCode(String hsCode) {
|
||||
this.hsCode = hsCode;
|
||||
}
|
||||
|
||||
public Number getTariffRate() {
|
||||
return tariffRate;
|
||||
}
|
||||
|
||||
public void setTariffRate(Number tariffRate) {
|
||||
this.tariffRate = tariffRate;
|
||||
}
|
||||
|
||||
public Double getOverseaShare() {
|
||||
return overseaShare;
|
||||
}
|
||||
|
|
@ -158,62 +114,6 @@ public class ReportDestinationDTO {
|
|||
this.safetyStock = safetyStock;
|
||||
}
|
||||
|
||||
public Double getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public void setWidth(Double width) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
public Double getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setHeight(Double height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public Double getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public void setLength(Double length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public Double getWeight() {
|
||||
return weight;
|
||||
}
|
||||
|
||||
public void setWeight(Double weight) {
|
||||
this.weight = weight;
|
||||
}
|
||||
|
||||
public DimensionUnit getDimensionUnit() {
|
||||
return dimensionUnit;
|
||||
}
|
||||
|
||||
public void setDimensionUnit(DimensionUnit dimensionUnit) {
|
||||
this.dimensionUnit = dimensionUnit;
|
||||
}
|
||||
|
||||
public WeightUnit getWeightUnit() {
|
||||
return weightUnit;
|
||||
}
|
||||
|
||||
public void setWeightUnit(WeightUnit weightUnit) {
|
||||
this.weightUnit = weightUnit;
|
||||
}
|
||||
|
||||
public Integer getHuUnitCount() {
|
||||
return huUnitCount;
|
||||
}
|
||||
|
||||
public void setHuUnitCount(Integer huUnitCount) {
|
||||
this.huUnitCount = huUnitCount;
|
||||
}
|
||||
|
||||
public Integer getLayer() {
|
||||
return layer;
|
||||
}
|
||||
|
|
|
|||
108
src/main/java/de/avatic/lcc/dto/report/ReportPremisesDTO.java
Normal file
108
src/main/java/de/avatic/lcc/dto/report/ReportPremisesDTO.java
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
package de.avatic.lcc.dto.report;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import de.avatic.lcc.model.db.utils.DimensionUnit;
|
||||
import de.avatic.lcc.model.db.utils.WeightUnit;
|
||||
|
||||
public class ReportPremisesDTO {
|
||||
|
||||
|
||||
@JsonProperty("hs_code")
|
||||
private String hsCode;
|
||||
|
||||
@JsonProperty("tariff_rate")
|
||||
private Number tariffRate;
|
||||
|
||||
|
||||
/* packaging */
|
||||
|
||||
private Double width;
|
||||
|
||||
private Double height;
|
||||
|
||||
private Double length;
|
||||
|
||||
private Double weight;
|
||||
|
||||
@JsonProperty("dimension_unit")
|
||||
private DimensionUnit dimensionUnit;
|
||||
|
||||
@JsonProperty("weight_unit")
|
||||
private WeightUnit weightUnit;
|
||||
|
||||
@JsonProperty("hu_unit_count")
|
||||
private Integer huUnitCount;
|
||||
|
||||
|
||||
public String getHsCode() {
|
||||
return hsCode;
|
||||
}
|
||||
|
||||
public void setHsCode(String hsCode) {
|
||||
this.hsCode = hsCode;
|
||||
}
|
||||
|
||||
public Number getTariffRate() {
|
||||
return tariffRate;
|
||||
}
|
||||
|
||||
public void setTariffRate(Number tariffRate) {
|
||||
this.tariffRate = tariffRate;
|
||||
}
|
||||
|
||||
public Double getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public void setWidth(Double width) {
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
public Double getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setHeight(Double height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public Double getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public void setLength(Double length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public Double getWeight() {
|
||||
return weight;
|
||||
}
|
||||
|
||||
public void setWeight(Double weight) {
|
||||
this.weight = weight;
|
||||
}
|
||||
|
||||
public DimensionUnit getDimensionUnit() {
|
||||
return dimensionUnit;
|
||||
}
|
||||
|
||||
public void setDimensionUnit(DimensionUnit dimensionUnit) {
|
||||
this.dimensionUnit = dimensionUnit;
|
||||
}
|
||||
|
||||
public WeightUnit getWeightUnit() {
|
||||
return weightUnit;
|
||||
}
|
||||
|
||||
public void setWeightUnit(WeightUnit weightUnit) {
|
||||
this.weightUnit = weightUnit;
|
||||
}
|
||||
|
||||
public Integer getHuUnitCount() {
|
||||
return huUnitCount;
|
||||
}
|
||||
|
||||
public void setHuUnitCount(Integer huUnitCount) {
|
||||
this.huUnitCount = huUnitCount;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package de.avatic.lcc.dto.report;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ReportSearchRequestDTO {
|
||||
|
||||
List<Integer> supplierIds;
|
||||
|
||||
List<Integer> materialIds;
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package de.avatic.lcc.service.bulk;
|
|||
import de.avatic.lcc.dto.bulk.BulkFileType;
|
||||
import de.avatic.lcc.model.bulk.BulkFileTypes;
|
||||
import de.avatic.lcc.model.bulk.BulkOperation;
|
||||
import de.avatic.lcc.model.db.materials.Material;
|
||||
import de.avatic.lcc.service.api.BatchGeoApiService;
|
||||
import de.avatic.lcc.service.bulk.bulkImport.*;
|
||||
import de.avatic.lcc.service.excelMapper.*;
|
||||
|
|
@ -11,6 +12,8 @@ import org.apache.poi.ss.usermodel.Sheet;
|
|||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.util.RecordFormatException;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
|
@ -34,6 +37,10 @@ public class BulkImportService {
|
|||
private final BatchGeoApiService batchGeoApiService;
|
||||
private final MaterialFastExcelMapper materialFastExcelMapper;
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(BulkImportService.class);
|
||||
|
||||
|
||||
|
||||
public BulkImportService(MatrixRateExcelMapper matrixRateExcelMapper, ContainerRateExcelMapper containerRateExcelMapper, MaterialExcelMapper materialExcelMapper, PackagingExcelMapper packagingExcelMapper, NodeExcelMapper nodeExcelMapper, NodeBulkImportService nodeBulkImportService, PackagingBulkImportService packagingBulkImportService, MaterialBulkImportService materialBulkImportService, MatrixRateImportService matrixRateImportService, ContainerRateImportService containerRateImportService, BatchGeoApiService batchGeoApiService, MaterialFastExcelMapper materialFastExcelMapper) {
|
||||
this.matrixRateExcelMapper = matrixRateExcelMapper;
|
||||
this.containerRateExcelMapper = containerRateExcelMapper;
|
||||
|
|
@ -63,7 +70,15 @@ public class BulkImportService {
|
|||
|
||||
private void processOperationWithFastExcel(BulkOperation op) throws IOException {
|
||||
var materials = materialFastExcelMapper.importFromExcel(op.getFile());
|
||||
materials.forEach(materialBulkImportService::processMaterialInstructions);
|
||||
|
||||
int processed = 0;
|
||||
for(var material : materials) {
|
||||
|
||||
if(processed++ % 1000 == 0)
|
||||
log.info("Processed {} of {} materials", processed, materials.size());
|
||||
|
||||
materialBulkImportService.processMaterialInstructions(material);
|
||||
}
|
||||
}
|
||||
|
||||
private void processOperationWithApachePOI(BulkOperation op) throws IOException {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import de.avatic.lcc.dto.generic.TransportType;
|
|||
import de.avatic.lcc.dto.report.ReportDTO;
|
||||
import de.avatic.lcc.dto.report.ReportDestinationDTO;
|
||||
import de.avatic.lcc.dto.report.ReportEntryDTO;
|
||||
import de.avatic.lcc.dto.report.ReportPremisesDTO;
|
||||
import de.avatic.lcc.service.bulk.helper.HeaderCellStyleProvider;
|
||||
import de.avatic.lcc.service.bulk.helper.HeaderGenerator;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
|
|
@ -140,11 +141,26 @@ public class ExcelReportingService {
|
|||
|
||||
// TODO: hardcoded (otherwise values wont match
|
||||
report.getCost().keySet().forEach(costName -> addData(costName, report.getCost().get(costName)));
|
||||
report.getRisk().keySet().forEach(riskName -> addData(riskName, report.getRisk().get(riskName)));
|
||||
report.getOverview().keySet().forEach(riskName -> addData(riskName, report.getOverview().get(riskName)));
|
||||
|
||||
commonPremisses(report.getPremises());
|
||||
|
||||
report.getDestinations().forEach(this::flattenDestination);
|
||||
}
|
||||
|
||||
private void commonPremisses(ReportPremisesDTO premises) {
|
||||
addData(DESTINATION_HS_CODE, premises.getHsCode());
|
||||
addData(DESTINATION_TARIFF_RATE, premises.getTariffRate().toString());
|
||||
|
||||
addData(DESTINATION_WIDTH, premises.getWidth().toString());
|
||||
addData(DESTINATION_HEIGHT, premises.getHeight().toString());
|
||||
addData(DESTINATION_LENGTH, premises.getLength().toString());
|
||||
addData(DESTINATION_DIMENSION_UNIT, premises.getDimensionUnit().toString());
|
||||
addData(DESTINATION_WEIGHT, premises.getWeight().toString());
|
||||
addData(DESTINATION_WEIGHT_UNIT, premises.getWeightUnit().toString());
|
||||
addData(DESTINATION_HU_UNIT_COUNT, premises.getHuUnitCount().toString());
|
||||
}
|
||||
|
||||
private void flattenDestination(ReportDestinationDTO destination) {
|
||||
|
||||
var hasMainRun = destination.getSections().stream().anyMatch(s -> s.getTransportType().equals(TransportType.RAIL) || s.getTransportType().equals(TransportType.SEA));
|
||||
|
|
@ -153,8 +169,7 @@ public class ExcelReportingService {
|
|||
addData(DESTINATION_ADDRESS, destination.getDestination().getAddress());
|
||||
|
||||
addData(DESTINATION_QUANTITY, destination.getAnnualQuantity().toString());
|
||||
addData(DESTINATION_HS_CODE, destination.getHsCode());
|
||||
addData(DESTINATION_TARIFF_RATE, destination.getTariffRate().toString());
|
||||
|
||||
addData(DESTINATION_OVERSHARE, destination.getOverseaShare().toString());
|
||||
|
||||
if(destination.getAirFreightShare() != null)
|
||||
|
|
@ -162,13 +177,7 @@ public class ExcelReportingService {
|
|||
addData(DESTINATION_TRANSPORT_TIME, destination.getTransportTime().toString());
|
||||
addData(DESTINATION_SAFETY_STOCK, destination.getSafetyStock().toString());
|
||||
|
||||
addData(DESTINATION_WIDTH, destination.getWidth().toString());
|
||||
addData(DESTINATION_HEIGHT, destination.getHeight().toString());
|
||||
addData(DESTINATION_LENGTH, destination.getLength().toString());
|
||||
addData(DESTINATION_DIMENSION_UNIT, destination.getDimensionUnit().toString());
|
||||
addData(DESTINATION_WEIGHT, destination.getWeight().toString());
|
||||
addData(DESTINATION_WEIGHT_UNIT, destination.getWeightUnit().toString());
|
||||
addData(DESTINATION_HU_UNIT_COUNT, destination.getHuUnitCount().toString());
|
||||
|
||||
addData(DESTINATION_CONTAINER_LAYER, !hasMainRun ? "-" : destination.getLayer().toString());
|
||||
|
||||
addData(DESTINATION_CONTAINER_UNIT_COUNT, !hasMainRun ? "-" : destination.getUnitCount().toString());
|
||||
|
|
|
|||
|
|
@ -2,10 +2,7 @@ package de.avatic.lcc.service.transformer.report;
|
|||
|
||||
import de.avatic.lcc.dto.generic.NodeType;
|
||||
import de.avatic.lcc.dto.generic.RateType;
|
||||
import de.avatic.lcc.dto.report.ReportDTO;
|
||||
import de.avatic.lcc.dto.report.ReportDestinationDTO;
|
||||
import de.avatic.lcc.dto.report.ReportEntryDTO;
|
||||
import de.avatic.lcc.dto.report.ReportSectionDTO;
|
||||
import de.avatic.lcc.dto.report.*;
|
||||
import de.avatic.lcc.model.db.calculations.CalculationJob;
|
||||
import de.avatic.lcc.model.db.calculations.CalculationJobDestination;
|
||||
import de.avatic.lcc.model.db.calculations.CalculationJobRouteSection;
|
||||
|
|
@ -67,23 +64,18 @@ public class ReportTransformer {
|
|||
|
||||
public ReportDTO toReportDTO(CalculationJob job) {
|
||||
|
||||
ReportDTO reportDTO = new ReportDTO();
|
||||
|
||||
var reportingProperty = propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.REPORTING).orElseThrow();
|
||||
boolean includeAirfreight = reportingProperty.getCurrentValue().equals("MEK_C");
|
||||
|
||||
/*
|
||||
* STEP 1: Fetch all infos.
|
||||
*/
|
||||
List<CalculationJobDestination> destinations = calculationJobDestinationRepository.getDestinationsByJobId(job.getId());
|
||||
Map<Integer, List<CalculationJobRouteSection>> sections = calculationJobRouteSectionRepository.getRouteSectionsByDestinationIds(destinations.stream().map(CalculationJobDestination::getId).toList());
|
||||
|
||||
var reportingProperty = propertyRepository.getPropertyByMappingId(SystemPropertyMappingId.REPORTING).orElseThrow();
|
||||
boolean includeAirfreight = reportingProperty.getCurrentValue().equals("MEK_C");
|
||||
var weightedTotalCost = getWeightedTotalCosts(sections);
|
||||
|
||||
Premise premise = premiseRepository.getPremiseById(job.getPremiseId()).orElseThrow();
|
||||
reportDTO.setMaterial(materialTransformer.toMaterialDTO(materialRepository.getByIdIncludeDeprecated(premise.getMaterialId()).orElseThrow()));
|
||||
|
||||
var period = getPeriod(job);
|
||||
|
||||
reportDTO.setStartDate(period.startDate);
|
||||
reportDTO.setEndDate(period.endDate);
|
||||
|
||||
Node sourceNode = null;
|
||||
|
||||
|
|
@ -93,10 +85,29 @@ public class ReportTransformer {
|
|||
if (premise.getUserSupplierNodeId() != null)
|
||||
sourceNode = userNodeRepository.getById(premise.getUserSupplierNodeId()).orElseThrow();
|
||||
|
||||
/*
|
||||
* STEP 2: Create report DTO.
|
||||
*/
|
||||
|
||||
ReportDTO reportDTO = new ReportDTO();
|
||||
|
||||
reportDTO.setMaterial(materialTransformer.toMaterialDTO(materialRepository.getByIdIncludeDeprecated(premise.getMaterialId()).orElseThrow()));
|
||||
|
||||
var period = getPeriod(job);
|
||||
|
||||
reportDTO.setStartDate(period.startDate);
|
||||
reportDTO.setEndDate(period.endDate);
|
||||
|
||||
reportDTO.setPremises(getPremisesDTO(job, premise));
|
||||
|
||||
|
||||
if (!destinations.isEmpty()) {
|
||||
reportDTO.setCost(getCostMap(job, destinations, weightedTotalCost, includeAirfreight));
|
||||
reportDTO.setRisk(getRisk(job, destinations, weightedTotalCost, includeAirfreight));
|
||||
var costs = getCostMap(job, destinations, weightedTotalCost, includeAirfreight);
|
||||
reportDTO.setCost(costs);
|
||||
|
||||
/* costs.total contains all logistic costs */
|
||||
reportDTO.setOverview(getOverview(job, destinations, weightedTotalCost, includeAirfreight, costs));
|
||||
|
||||
Node finalSourceNode = sourceNode;
|
||||
reportDTO.setDestination(destinations.stream().map(d -> getDestinationDTO(d, sections.get(d.getId()), premise, finalSourceNode, includeAirfreight)).toList());
|
||||
} else {
|
||||
|
|
@ -160,12 +171,32 @@ public class ReportTransformer {
|
|||
return new WeightedTotalCosts(totalPreRunCost, totalMainRunCost, totalPostRunCost, totalD2D, totalCost);
|
||||
}
|
||||
|
||||
private ReportDestinationDTO getDestinationDTO(CalculationJobDestination destination, List<CalculationJobRouteSection> sections, Premise premise, Node sourceNode, boolean includeAirfreight) {
|
||||
var destinationNode = nodeRepository.getByDestinationId(destination.getPremiseDestinationId()).orElseThrow();
|
||||
private ReportPremisesDTO getPremisesDTO(CalculationJob job, Premise premise) {
|
||||
ReportPremisesDTO premisesDTO = new ReportPremisesDTO();
|
||||
|
||||
var dimensionUnit = premise.getHuDisplayedDimensionUnit();
|
||||
var weightUnit = premise.getHuDisplayedWeightUnit();
|
||||
|
||||
premisesDTO.setDimensionUnit(dimensionUnit);
|
||||
premisesDTO.setWeightUnit(weightUnit);
|
||||
premisesDTO.setHeight(dimensionUnit.convertFromMM(premise.getIndividualHuHeight()));
|
||||
premisesDTO.setWidth(dimensionUnit.convertFromMM(premise.getIndividualHuWidth()));
|
||||
premisesDTO.setLength(dimensionUnit.convertFromMM(premise.getIndividualHuLength()));
|
||||
premisesDTO.setWeight(weightUnit.convertFromG(premise.getIndividualHuWeight()));
|
||||
premisesDTO.setHuUnitCount(premise.getHuUnitCount());
|
||||
|
||||
premisesDTO.setHsCode(premise.getHsCode());
|
||||
premisesDTO.setTariffRate(premise.getTariffRate());
|
||||
|
||||
|
||||
return premisesDTO;
|
||||
}
|
||||
|
||||
private ReportDestinationDTO getDestinationDTO(CalculationJobDestination destination, List<CalculationJobRouteSection> sections, Premise premise, Node sourceNode, boolean includeAirfreight) {
|
||||
var destinationNode = nodeRepository.getByDestinationId(destination.getPremiseDestinationId()).orElseThrow();
|
||||
|
||||
|
||||
|
||||
ReportDestinationDTO destinationDTO = new ReportDestinationDTO();
|
||||
destinationDTO.setSections(sections.stream().map(s -> getSection(s, sourceNode, destinationNode, premise)).toList());
|
||||
|
||||
|
|
@ -182,14 +213,6 @@ public class ReportTransformer {
|
|||
|
||||
destinationDTO.setDestination(nodeTransformer.toNodeDTO(destinationNode));
|
||||
|
||||
destinationDTO.setDimensionUnit(dimensionUnit);
|
||||
destinationDTO.setWeightUnit(weightUnit);
|
||||
destinationDTO.setHeight(dimensionUnit.convertFromMM(premise.getIndividualHuHeight()));
|
||||
destinationDTO.setWidth(dimensionUnit.convertFromMM(premise.getIndividualHuWidth()));
|
||||
destinationDTO.setLength(dimensionUnit.convertFromMM(premise.getIndividualHuLength()));
|
||||
destinationDTO.setWeight(weightUnit.convertFromG(premise.getIndividualHuWeight()));
|
||||
destinationDTO.setHuUnitCount(premise.getHuUnitCount());
|
||||
|
||||
CalculationJobRouteSection mainRun = sections.stream().filter(CalculationJobRouteSection::getMainRun).findFirst().orElse(null);
|
||||
|
||||
destinationDTO.setLayer(destination.getLayerCount());
|
||||
|
|
@ -209,8 +232,7 @@ public class ReportTransformer {
|
|||
destinationDTO.setUnitCount(destination.getHuCount());
|
||||
destinationDTO.setWeightExceeded(destination.getTransportWeightExceeded());
|
||||
|
||||
destinationDTO.setHsCode(premise.getHsCode());
|
||||
destinationDTO.setTariffRate(destination.getTariffRate());
|
||||
|
||||
|
||||
return destinationDTO;
|
||||
}
|
||||
|
|
@ -247,7 +269,7 @@ public class ReportTransformer {
|
|||
return sectionDTO;
|
||||
}
|
||||
|
||||
private Map<String, ReportEntryDTO> getRisk(CalculationJob job, List<CalculationJobDestination> destination, WeightedTotalCosts weightedTotalCost, boolean includeAirfreight) {
|
||||
private Map<String, ReportEntryDTO> getOverview(CalculationJob job, List<CalculationJobDestination> destination, WeightedTotalCosts weightedTotalCost, boolean includeAirfreight, Map<String, ReportEntryDTO> weightedCostBreakDown) {
|
||||
Map<String, ReportEntryDTO> risk = new HashMap<>();
|
||||
|
||||
var annualAmount = destination.stream().map(CalculationJobDestination::getAnnualAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
|
@ -261,6 +283,18 @@ public class ReportTransformer {
|
|||
|
||||
totalValue = totalValue.add(airfreightValue.multiply(BigDecimal.valueOf(includeAirfreight ? 1 : 0)));
|
||||
|
||||
var totalLogistics = getTotalLogistics(weightedCostBreakDown);
|
||||
|
||||
ReportEntryDTO mekA = new ReportEntryDTO();
|
||||
mekA.setTotal(weightedCostBreakDown.get("mek_a").getTotal());
|
||||
mekA.setPercentage(weightedCostBreakDown.get("mek_a").getTotal().doubleValue() / (totalValue.doubleValue()));
|
||||
risk.put("mek_a", mekA);
|
||||
|
||||
ReportEntryDTO logistics = new ReportEntryDTO();
|
||||
logistics.setTotal(totalLogistics);
|
||||
logistics.setPercentage(totalLogistics / (totalValue.doubleValue()));
|
||||
risk.put("logistics", logistics);
|
||||
|
||||
ReportEntryDTO total = new ReportEntryDTO();
|
||||
total.setTotal(totalValue);
|
||||
total.setPercentage(totalValue.divide(totalValue, 4, RoundingMode.HALF_UP));
|
||||
|
|
@ -279,6 +313,11 @@ public class ReportTransformer {
|
|||
return risk;
|
||||
}
|
||||
|
||||
private Double getTotalLogistics(Map<String, ReportEntryDTO> weightedCostBreakDown) {
|
||||
return weightedCostBreakDown.get("total").getTotal().doubleValue() - weightedCostBreakDown.get("mek_a").getTotal().doubleValue();
|
||||
|
||||
}
|
||||
|
||||
private Map<String, ReportEntryDTO> getCostMap(CalculationJob job, List<CalculationJobDestination> destination, WeightedTotalCosts weightedTotalCost, boolean includeAirfreight) {
|
||||
Map<String, ReportEntryDTO> cost = new HashMap<>();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue