Bugfix: fixed reporting for user generated nodes
This commit is contained in:
parent
a2e0029dfe
commit
b19e16fbb0
12 changed files with 107 additions and 54 deletions
|
|
@ -15,15 +15,15 @@
|
|||
<div class="caption">Select suppliers to compare:</div>
|
||||
<div class="content-container" v-if="suppliers.length !== 0">
|
||||
<transition-group name="fade" tag="ul" class="item-list">
|
||||
<li class="item-list-element" v-for="supplier in suppliers" :key="supplier.id"
|
||||
@click="selectSupplier(supplier.id)">
|
||||
<li class="item-list-element" v-for="supplier in suppliers" :key="supplier.is_user_node? 'u' : 'n'+ String(supplier.id)"
|
||||
@click="selectSupplier(supplier)">
|
||||
<supplier-item :id="String(supplier.id)"
|
||||
:iso-code="supplier.country.iso_code"
|
||||
:address="supplier.address"
|
||||
:name="supplier.name"
|
||||
:show-trash="false"
|
||||
:is-user-supplier="supplier.isUserSupplier"
|
||||
:selected="isSelected(supplier.id)"
|
||||
:selected="isSelected(supplier)"
|
||||
></supplier-item>
|
||||
</li>
|
||||
</transition-group>
|
||||
|
|
@ -64,7 +64,7 @@ export default {
|
|||
return this.reportSearchStore.getSuppliers;
|
||||
},
|
||||
hasSelectedSuppliers() {
|
||||
return this.reportSearchStore.getSelectedIds.length > 0;
|
||||
return this.reportSearchStore.getSelectedSuppliers.length > 0;
|
||||
},
|
||||
partNumber() {
|
||||
return this.reportSearchStore.getMaterial?.part_number;
|
||||
|
|
@ -72,14 +72,18 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
close(action) {
|
||||
const ids = this.reportSearchStore.getSelectedIds;
|
||||
this.$emit('close', {action: action, supplierIds: ids, materialId: this.selectedMaterialId});
|
||||
const suppliers = this.reportSearchStore.getSelectedSuppliers;
|
||||
this.$emit('close', {
|
||||
action: action,
|
||||
userSupplierIds: suppliers.filter(s => s.is_user_node).map(s => s.id),
|
||||
supplierIds: suppliers.filter(s => !s.is_user_node).map(s => s.id),
|
||||
materialId: this.selectedMaterialId});
|
||||
},
|
||||
isSelected(id) {
|
||||
return this.reportSearchStore.isSelected(id);
|
||||
isSelected(supplier) {
|
||||
return this.reportSearchStore.isSelected(supplier);
|
||||
},
|
||||
selectSupplier(id) {
|
||||
this.reportSearchStore.selectSupplier(id);
|
||||
selectSupplier(supplier) {
|
||||
this.reportSearchStore.selectSupplier(supplier);
|
||||
},
|
||||
async getMaterial(query) {
|
||||
const materialQuery = {searchTerm: query};
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ export default {
|
|||
},
|
||||
async closeModal(data) {
|
||||
if (data.action === 'accept') {
|
||||
await this.reportsStore.fetchReports(data.materialId, data.supplierIds);
|
||||
await this.reportsStore.fetchReports(data.materialId, data.supplierIds, data.userSupplierIds);
|
||||
}
|
||||
this.showModal = false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ export const useReportSearchStore = defineStore('reportSearch', {
|
|||
state() {
|
||||
return {
|
||||
suppliers: [],
|
||||
selectedIds: [],
|
||||
selectedSuppliers: [],
|
||||
remainingSuppliers: [],
|
||||
material: null,
|
||||
loading: false,
|
||||
|
|
@ -17,12 +17,12 @@ export const useReportSearchStore = defineStore('reportSearch', {
|
|||
return state.remainingSuppliers;
|
||||
},
|
||||
isSelected(state) {
|
||||
return function (id) {
|
||||
return state.selectedIds.includes(id);
|
||||
return function (supplier) {
|
||||
return state.selectedSuppliers.some(s => s.id === supplier.id && s.is_user_node === supplier.is_user_node);
|
||||
}
|
||||
},
|
||||
getSelectedIds(state) {
|
||||
return state.selectedIds;
|
||||
getSelectedSuppliers(state) {
|
||||
return state.selectedSuppliers;
|
||||
},
|
||||
getMaterialId(state) {
|
||||
return state.material?.id;
|
||||
|
|
@ -35,16 +35,16 @@ export const useReportSearchStore = defineStore('reportSearch', {
|
|||
async reset() {
|
||||
this.suppliers = [];
|
||||
this.material = null;
|
||||
this.selectedIds = [];
|
||||
this.selectedSuppliers = [];
|
||||
this.remainingSuppliers = [];
|
||||
},
|
||||
async selectSupplier(id) {
|
||||
async selectSupplier(supplier) {
|
||||
|
||||
if (!this.selectedIds.includes(id))
|
||||
this.selectedIds.push(id)
|
||||
if (!this.selectedSuppliers.some(s => s.id === supplier.id && s.is_user_node === supplier.is_user_node))
|
||||
this.selectedSuppliers.push(supplier)
|
||||
else {
|
||||
const index = this.selectedIds.findIndex(selectedId => selectedId === id);
|
||||
this.selectedIds.splice(index, 1);
|
||||
const index = this.selectedSuppliers.findIndex(s => s.id === supplier.id && s.is_user_node === supplier.is_user_node);
|
||||
this.selectedSuppliers.splice(index, 1);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ export const useReportSearchStore = defineStore('reportSearch', {
|
|||
const toBeShown = []
|
||||
|
||||
this.suppliers.forEach(supplierList => {
|
||||
const shouldInclude = this.selectedIds.every(id => supplierList.some(s => s.id === id))
|
||||
const shouldInclude = this.selectedSuppliers.every(selSup => supplierList.some(s => s.id === selSup.id && s.is_user_node === selSup.is_user_node))
|
||||
|
||||
if (shouldInclude)
|
||||
toBeShown.push(...supplierList);
|
||||
|
|
@ -62,7 +62,7 @@ export const useReportSearchStore = defineStore('reportSearch', {
|
|||
|
||||
|
||||
this.remainingSuppliers = toBeShown.filter((item, index) =>
|
||||
toBeShown.findIndex(obj => obj.id === item.id) === index
|
||||
toBeShown.findIndex(obj => obj.id === item.id && obj.is_user_node === item.is_user_node) === index
|
||||
);
|
||||
},
|
||||
async setMaterial(material) {
|
||||
|
|
@ -74,7 +74,7 @@ export const useReportSearchStore = defineStore('reportSearch', {
|
|||
|
||||
this.loading = true;
|
||||
this.suppliers = [];
|
||||
this.selectedIds = [];
|
||||
this.selectedSuppliers = [];
|
||||
this.remainingSuppliers = [];
|
||||
|
||||
const params = new URLSearchParams();
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ export const useReportsStore = defineStore('reports', {
|
|||
loading: false,
|
||||
materialId: null,
|
||||
supplierIds: [],
|
||||
userSupplierIds: [],
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
|
|
@ -46,7 +47,7 @@ export const useReportsStore = defineStore('reports', {
|
|||
reset() {
|
||||
this.reports = [];
|
||||
},
|
||||
async fetchReports(materialId, supplierIds) {
|
||||
async fetchReports(materialId, supplierIds, userSupplierIds) {
|
||||
if (supplierIds == null || materialId == null) return;
|
||||
|
||||
this.loading = true;
|
||||
|
|
@ -54,10 +55,12 @@ export const useReportsStore = defineStore('reports', {
|
|||
|
||||
this.materialId = materialId;
|
||||
this.supplierIds = supplierIds;
|
||||
this.userSupplierIds = userSupplierIds;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.append('material', materialId);
|
||||
params.append('sources', supplierIds);
|
||||
params.append('userSources', userSupplierIds);
|
||||
|
||||
const url = `${config.backendUrl}/reports/view/${params.size === 0 ? '' : '?'}${params.toString()}`;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import de.avatic.lcc.dto.generic.NodeDTO;
|
|||
import de.avatic.lcc.dto.report.ReportDTO;
|
||||
import de.avatic.lcc.service.report.ExcelReportingService;
|
||||
import de.avatic.lcc.service.report.ReportingService;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import org.springframework.core.io.InputStreamResource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
|
|
@ -56,9 +55,9 @@ public class ReportingController {
|
|||
* @param nodeIds A list of node IDs (sources) to include in the report.
|
||||
* @return The generated report details.
|
||||
*/
|
||||
@GetMapping({"/view","/view/"})
|
||||
public ResponseEntity<List<ReportDTO>> getReport(@RequestParam(value = "material") Integer materialId, @RequestParam(value = "sources") List<Integer> nodeIds) {
|
||||
return ResponseEntity.ok(reportingService.getReport(materialId, nodeIds));
|
||||
@GetMapping({"/view", "/view/"})
|
||||
public ResponseEntity<List<ReportDTO>> getReport(@RequestParam(value = "material") Integer materialId, @RequestParam(value = "sources", required = false) List<Integer> nodeIds, @RequestParam(value = "userSources", required = false) List<Integer> userNodeIds) {
|
||||
return ResponseEntity.ok(reportingService.getReport(materialId, nodeIds, userNodeIds));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -68,8 +67,8 @@ public class ReportingController {
|
|||
* @param nodeIds A list of node IDs (sources) to include in the downloaded report.
|
||||
* @return The Excel file as an attachment in the response.
|
||||
*/
|
||||
@GetMapping({"/download","/download/"})
|
||||
public ResponseEntity<InputStreamResource> downloadReport(@RequestParam(value = "material") Integer materialId, @RequestParam(value = "sources") List<Integer> nodeIds) {
|
||||
@GetMapping({"/download", "/download/"})
|
||||
public ResponseEntity<InputStreamResource> downloadReport(@RequestParam(value = "material") Integer materialId, @RequestParam(value = "sources", required = false) List<Integer> nodeIds, @RequestParam(value = "userSources", required = false) List<Integer> userNodeIds) {
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.add("Content-Disposition", "attachment; filename=lcc_report.xlsx");
|
||||
|
|
@ -78,6 +77,6 @@ public class ReportingController {
|
|||
.ok()
|
||||
.headers(headers)
|
||||
.contentType(MediaType.parseMediaType("application/vnd.ms-excel"))
|
||||
.body(new InputStreamResource(excelReportingService.generateExcelReport(materialId, nodeIds)));
|
||||
.body(new InputStreamResource(excelReportingService.generateExcelReport(materialId, nodeIds, userNodeIds)));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,6 +86,22 @@ public class CalculationJobRepository {
|
|||
return Optional.of(job.getFirst());
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Optional<CalculationJob> getCalculationJobWithJobStateValidUserNodeId(Integer periodId, Integer userNodeId, Integer materialId) {
|
||||
|
||||
/* there should only be one job per period id, node id and material id combination */
|
||||
String query = "SELECT * FROM calculation_job AS cj INNER JOIN premise AS p ON cj.premise_id = p.id WHERE job_state = 'VALID' AND validity_period_id = ? AND p.user_supplier_node_id = ? AND material_id = ? ORDER BY cj.calculation_date DESC LIMIT 1";
|
||||
|
||||
var job = jdbcTemplate.query(query, new CalculationJobMapper(), periodId, userNodeId, materialId);
|
||||
|
||||
if(job.isEmpty())
|
||||
return Optional.empty();
|
||||
|
||||
return Optional.of(job.getFirst());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Transactional
|
||||
public void setStateTo(Integer id, CalculationJobState calculationJobState) {
|
||||
String sql = "UPDATE calculation_job SET job_state = ? WHERE id = ?";
|
||||
|
|
|
|||
|
|
@ -732,7 +732,7 @@ public class PremiseRepository {
|
|||
user_n.country_id as 'user_n.country_id', user_n.geo_lat as 'user_n.geo_lat', user_n.geo_lng as 'user_n.geo_lng'
|
||||
""").append(BASE_JOIN_QUERY);
|
||||
appendConditions(queryBuilder);
|
||||
queryBuilder.append(" ORDER BY p.updated_at, p.id DESC");
|
||||
queryBuilder.append(" ORDER BY p.updated_at DESC, p.id DESC");
|
||||
queryBuilder.append(" LIMIT ? OFFSET ?");
|
||||
return queryBuilder.toString();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -189,13 +189,33 @@ public class ValidityPeriodRepository {
|
|||
}
|
||||
|
||||
@Transactional
|
||||
public Optional<ValidityPeriod> getValidPeriodForReportingByMaterialId(Integer materialId, List<Integer> nodeIds) {
|
||||
public Optional<ValidityPeriod> getValidPeriodForReportingByMaterialId(Integer materialId, List<Integer> nodeIds, List<Integer> userNodeIds) {
|
||||
|
||||
if (nodeIds == null || nodeIds.isEmpty()) {
|
||||
// Check if both lists are empty
|
||||
if ((nodeIds == null || nodeIds.isEmpty()) && (userNodeIds == null || userNodeIds.isEmpty())) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
String placeholders = String.join(",", Collections.nCopies(nodeIds.size(), "?"));
|
||||
// Handle null lists
|
||||
List<Integer> safeNodeIds = nodeIds != null ? nodeIds : Collections.emptyList();
|
||||
List<Integer> safeUserNodeIds = userNodeIds != null ? userNodeIds : Collections.emptyList();
|
||||
|
||||
int totalNodeCount = safeNodeIds.size() + safeUserNodeIds.size();
|
||||
|
||||
// Build placeholders for both node types
|
||||
String nodePlaceholders = safeNodeIds.isEmpty() ? "" : String.join(",", Collections.nCopies(safeNodeIds.size(), "?"));
|
||||
String userNodePlaceholders = safeUserNodeIds.isEmpty() ? "" : String.join(",", Collections.nCopies(safeUserNodeIds.size(), "?"));
|
||||
|
||||
// Build the WHERE clause dynamically
|
||||
StringBuilder whereClause = new StringBuilder();
|
||||
if (!safeNodeIds.isEmpty() && !safeUserNodeIds.isEmpty()) {
|
||||
whereClause.append("AND (p.supplier_node_id IN (").append(nodePlaceholders)
|
||||
.append(") OR p.user_supplier_node_id IN (").append(userNodePlaceholders).append("))");
|
||||
} else if (!safeNodeIds.isEmpty()) {
|
||||
whereClause.append("AND p.supplier_node_id IN (").append(nodePlaceholders).append(")");
|
||||
} else {
|
||||
whereClause.append("AND p.user_supplier_node_id IN (").append(userNodePlaceholders).append(")");
|
||||
}
|
||||
|
||||
String validityPeriodSql = """
|
||||
SELECT vp.*
|
||||
|
|
@ -203,33 +223,42 @@ public class ValidityPeriodRepository {
|
|||
INNER JOIN (
|
||||
SELECT
|
||||
cj.validity_period_id,
|
||||
COUNT(DISTINCT p.supplier_node_id) as node_count
|
||||
COUNT(DISTINCT COALESCE(p.supplier_node_id, p.user_supplier_node_id)) as node_count
|
||||
FROM
|
||||
premise p
|
||||
INNER JOIN
|
||||
calculation_job cj ON p.id = cj.premise_id
|
||||
WHERE
|
||||
p.material_id = ?
|
||||
AND p.supplier_node_id IN ("""
|
||||
+ placeholders + """
|
||||
)
|
||||
"""
|
||||
+ whereClause + """
|
||||
|
||||
GROUP BY
|
||||
cj.validity_period_id
|
||||
HAVING
|
||||
COUNT(DISTINCT p.supplier_node_id) = ?
|
||||
COUNT(DISTINCT COALESCE(p.supplier_node_id, p.user_supplier_node_id)) = ?
|
||||
) matching_periods ON vp.id = matching_periods.validity_period_id
|
||||
ORDER BY
|
||||
vp.start_date DESC
|
||||
LIMIT 1
|
||||
""";
|
||||
|
||||
// Build parameters array
|
||||
Object[] params = new Object[1 + totalNodeCount + 1];
|
||||
int paramIndex = 0;
|
||||
params[paramIndex++] = materialId;
|
||||
|
||||
Object[] params = new Object[1 + nodeIds.size() + 1];
|
||||
params[0] = materialId;
|
||||
for (int i = 0; i < nodeIds.size(); i++) {
|
||||
params[i + 1] = nodeIds.get(i);
|
||||
// Add nodeIds
|
||||
for (Integer nodeId : safeNodeIds) {
|
||||
params[paramIndex++] = nodeId;
|
||||
}
|
||||
params[params.length - 1] = nodeIds.size();
|
||||
|
||||
// Add userNodeIds
|
||||
for (Integer userNodeId : safeUserNodeIds) {
|
||||
params[paramIndex++] = userNodeId;
|
||||
}
|
||||
|
||||
params[paramIndex] = totalNodeCount;
|
||||
|
||||
var periods = jdbcTemplate.query(validityPeriodSql, new ValidityPeriodMapper(), params);
|
||||
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ public class BulkImportService {
|
|||
break;
|
||||
case NODE:
|
||||
var nodeInstructions = nodeExcelMapper.extractSheet(sheet);
|
||||
// batchGeoApiService.geocodeBatch(nodeInstructions);
|
||||
batchGeoApiService.geocodeBatch(nodeInstructions);
|
||||
nodeInstructions.forEach(nodeBulkImportService::processNodeInstructions);
|
||||
break;
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -34,9 +34,9 @@ public class ExcelReportingService {
|
|||
this.headerGenerator = headerGenerator;
|
||||
}
|
||||
|
||||
public ByteArrayResource generateExcelReport(Integer materialId, List<Integer> nodeIds) {
|
||||
public ByteArrayResource generateExcelReport(Integer materialId, List<Integer> nodeIds, List<Integer> userNodeIds ) {
|
||||
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
var reports = reportingService.getReport(materialId, nodeIds);
|
||||
var reports = reportingService.getReport(materialId, nodeIds, userNodeIds);
|
||||
|
||||
Workbook workbook = new XSSFWorkbook();
|
||||
Sheet sheet = workbook.createSheet("report");
|
||||
|
|
|
|||
|
|
@ -49,15 +49,17 @@ public class ReportingService {
|
|||
return nodes;
|
||||
}
|
||||
|
||||
public List<ReportDTO> getReport(Integer materialId, List<Integer> nodeIds) {
|
||||
var period = validityPeriodRepository.getValidPeriodForReportingByMaterialId(materialId, nodeIds);
|
||||
public List<ReportDTO> getReport(Integer materialId, List<Integer> nodeIds, List<Integer> userNodeIds) {
|
||||
var period = validityPeriodRepository.getValidPeriodForReportingByMaterialId(materialId, nodeIds, userNodeIds);
|
||||
|
||||
if (period.isEmpty())
|
||||
throw new IllegalArgumentException("No valid period found");
|
||||
|
||||
var periodId = period.get().getId();
|
||||
|
||||
var jobs = nodeIds.stream().map(nodeId -> calculationJobRepository.getCalculationJobWithJobStateValid(periodId, nodeId,materialId)).filter(Optional::isPresent).map(Optional::get).toList();
|
||||
var jobs = new ArrayList<>(nodeIds.stream().map(nodeId -> calculationJobRepository.getCalculationJobWithJobStateValid(periodId, nodeId, materialId)).filter(Optional::isPresent).map(Optional::get).toList());
|
||||
jobs.addAll(userNodeIds.stream().map(nodeId -> calculationJobRepository.getCalculationJobWithJobStateValidUserNodeId(periodId,nodeId ,materialId)).filter(Optional::isPresent).map(Optional::get).toList());
|
||||
|
||||
return jobs.stream().map(reportTransformer::toReportDTO).toList();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -239,7 +239,7 @@ CREATE TABLE IF NOT EXISTS container_rate
|
|||
FOREIGN KEY (validity_period_id) REFERENCES validity_period (id),
|
||||
INDEX idx_from_to_nodes (from_node_id, to_node_id),
|
||||
INDEX idx_validity_period_id (validity_period_id),
|
||||
CONSTRAINT uk_container_rate_unique UNIQUE (from_node_id, to_node_id, validity_period_id)
|
||||
CONSTRAINT uk_container_rate_unique UNIQUE (from_node_id, to_node_id, validity_period_id, container_rate_type)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS country_matrix_rate
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue