mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-16 10:34:31 +00:00
cost web ui:
- add "Linked rate" column, - assign a cost rate to multiple cost items by selecting linked rate cells & clicking the "assign" button on the chosen cost rate #5369
This commit is contained in:
@@ -1,58 +1,27 @@
|
||||
:root.blender .flex-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.card-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* CSS for the work schedule cards */
|
||||
:root.blender #work_schedules {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
:root.blender .card {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
width: 300px;
|
||||
margin: 10px;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
:root.blender .card:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
|
||||
:root.blender .card-title {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
:root.blender .card-text {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 20px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
:root.blender .btn-primary {
|
||||
background-color: #007bff;
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 1rem;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
:root.blender .btn-primary:hover {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
.card {
|
||||
background-color: var(--blender-even-row, var(--nav-bg-color));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background-color: var(--blender-panel-header, var(--bg-color));
|
||||
border: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
background-color: var(--blender-panel-background, var(--bg-color));
|
||||
border: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
@@ -392,7 +392,7 @@ form table {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.clickable-cell:hover {
|
||||
.selected-cell, .clickable-cell:hover {
|
||||
border: 2px solid var(--highlight-over);
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -560,3 +560,10 @@ i {
|
||||
background-color: #ccffcc;
|
||||
color: darkgreen;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 10px;
|
||||
}
|
||||
.card-body {
|
||||
height: 10vh;
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
@import url("./components/card.css");
|
||||
|
||||
:root {
|
||||
--font-family: Arial, sans-serif;
|
||||
--base-font-size: 16px;
|
||||
@@ -250,33 +252,7 @@ button:hover {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--blender-even-row, var(--nav-bg-color));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background-color: var(--blender-panel-header, var(--bg-color));
|
||||
border: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
background-color: var(--blender-panel-background, var(--bg-color));
|
||||
border: 1px solid var(--blender-tab-outline, var(--border-color));
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
li.svg-name {
|
||||
list-style-type: none;
|
||||
|
||||
@@ -3,8 +3,6 @@ import { CostUI } from "./utilities/costui.js";
|
||||
const connectedClients = {};
|
||||
let socket;
|
||||
|
||||
let loadedSchedules = {};
|
||||
|
||||
$(document).ready(function () {
|
||||
var defaultTheme = "blender";
|
||||
var theme = localStorage.getItem("theme") || defaultTheme;
|
||||
@@ -58,6 +56,19 @@ function handleEditQuantities(data) {
|
||||
});
|
||||
}
|
||||
|
||||
function assignCostValues(costRateId, callback){
|
||||
const selectedCostItems = CostUI.getSelectedCostItems();
|
||||
if (selectedCostItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
executeOperator({
|
||||
type: "assignCostValues",
|
||||
costRateId: costRateId,
|
||||
costItemIds: selectedCostItems,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function addSumCostValue(costItemId) {
|
||||
executeOperator({ type: "addSumCostValue", costItemId: costItemId });
|
||||
}
|
||||
@@ -276,9 +287,6 @@ function toggleClientList() {
|
||||
|
||||
function handleCostSchedulesData(data) {
|
||||
const blenderId = data.blenderId;
|
||||
const loadedSchedule = loadedSchedules[blenderId]
|
||||
? loadedSchedules[blenderId]
|
||||
: null;
|
||||
const costSchedules = data.data["cost_schedules"]["cost_schedules"];
|
||||
if (costSchedules.length === 0) {
|
||||
return;
|
||||
@@ -309,17 +317,10 @@ function handleCostSchedulesData(data) {
|
||||
);
|
||||
costScheduleDiv.append(card);
|
||||
});
|
||||
loadedSchedule ? CostUI.highlightElement("schedule-" + loadedSchedule) : null;
|
||||
}
|
||||
|
||||
function handleCostItemsData(data) {
|
||||
const costScheduleId = data.data["cost_items"]["schedule_data"][0]["id"];
|
||||
const cards = document.querySelectorAll("[id^='schedule-']");
|
||||
cards.forEach((card) => {
|
||||
card.classList.remove("highlighted");
|
||||
});
|
||||
CostUI.highlightElement("schedule-" + costScheduleId);
|
||||
loadedSchedules[data.blenderId] = costScheduleId;
|
||||
const currency = data.data["cost_items"]["currency"]
|
||||
? data.data["cost_items"]["currency"]["name"]
|
||||
: "Undefined";
|
||||
@@ -338,6 +339,7 @@ function handleCostItemsData(data) {
|
||||
enableEditingQuantities: enableEditingQuantities,
|
||||
addSumCostValue: addSumCostValue,
|
||||
enableEditingClassification: enableEditingClassification,
|
||||
assignCostValues: assignCostValues,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -70,66 +70,78 @@ export class CostUI {
|
||||
handleButtonClick(editCostValuesButton, callbacks.enableEditingCostValues);
|
||||
handleButtonClick(addButton, callbacks.addCostItem);
|
||||
handleButtonClick(deleteCostItemButton, callbacks.deleteCostItem);
|
||||
|
||||
addTableListeners(callbacks);
|
||||
|
||||
function addTableListeners(callbacks) {
|
||||
function getColumnNames(tableId) {
|
||||
const table = document.getElementById(tableId);
|
||||
const headerRow = table.querySelector("thead tr");
|
||||
return Array.from(headerRow.children).map((headerCell) =>
|
||||
headerCell.textContent.trim()
|
||||
);
|
||||
}
|
||||
document
|
||||
.getElementById("cost-items")
|
||||
.addEventListener("click", function (event) {
|
||||
const targetRow = event.target.closest("tr");
|
||||
const targetCell = event.target.closest("td");
|
||||
if (targetRow && targetCell) {
|
||||
const columnIndex = Array.from(targetRow.children).indexOf(
|
||||
targetCell
|
||||
);
|
||||
const tableId = targetRow.parentElement.parentElement.id;
|
||||
const costItemId = parseInt(targetRow.getAttribute("id"));
|
||||
const columnName = getColumnNames(tableId)[columnIndex];
|
||||
if (
|
||||
(columnName.includes("Cost") || columnName.includes("Rate")) &&
|
||||
!columnName.includes("Total")
|
||||
) {
|
||||
callbacks.enableEditingCostValues
|
||||
? callbacks.enableEditingCostValues(costItemId)
|
||||
: null;
|
||||
}
|
||||
if (columnName === "Quantity") {
|
||||
callbacks.enableEditingQuantities
|
||||
? callbacks.enableEditingQuantities(costItemId)
|
||||
: null;
|
||||
}
|
||||
if (columnName === "Classification") {
|
||||
callbacks.enableEditingClassification
|
||||
? callbacks.enableEditingClassification(costItemId)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static getColumnPreferences() {
|
||||
const preferences = localStorage.getItem("columnPreferences");
|
||||
return preferences
|
||||
? JSON.parse(preferences)
|
||||
: {
|
||||
ID: true,
|
||||
Name: true,
|
||||
Quantity: true,
|
||||
Unit: true,
|
||||
Cost: true,
|
||||
TotalCost: true,
|
||||
Classification: true,
|
||||
Actions: true,
|
||||
};
|
||||
static addTableCallbacks(callbacks, table) {
|
||||
if (table.listenersAdded) {
|
||||
return;
|
||||
}
|
||||
|
||||
function getColumnNames(tableId) {
|
||||
const table = document.getElementById(tableId);
|
||||
const headerRow = table.querySelector("thead tr");
|
||||
return Array.from(headerRow.children).map((headerCell) =>
|
||||
headerCell.textContent.trim()
|
||||
);
|
||||
}
|
||||
|
||||
function addTableListeners(callbacks) {
|
||||
table.addEventListener("click", function (event) {
|
||||
const targetRow = event.target.closest("tr");
|
||||
const targetCell = event.target.closest("td");
|
||||
if (targetRow && targetCell) {
|
||||
const columnIndex = Array.from(targetRow.children).indexOf(
|
||||
targetCell
|
||||
);
|
||||
const tableId = table.id;
|
||||
const costItemId = parseInt(targetRow.getAttribute("id"));
|
||||
const columnName = getColumnNames(tableId)[columnIndex];
|
||||
|
||||
if (
|
||||
(columnName.includes("Cost") || columnName.includes("Rate")) &&
|
||||
!columnName.includes("Total") &&
|
||||
!columnName.includes("Linked")
|
||||
) {
|
||||
callbacks.enableEditingCostValues
|
||||
? callbacks.enableEditingCostValues(costItemId)
|
||||
: null;
|
||||
}
|
||||
|
||||
if (columnName === "Quantity") {
|
||||
callbacks.enableEditingQuantities
|
||||
? callbacks.enableEditingQuantities(costItemId)
|
||||
: null;
|
||||
}
|
||||
|
||||
if (columnName === "Classification") {
|
||||
callbacks.enableEditingClassification
|
||||
? callbacks.enableEditingClassification(costItemId)
|
||||
: null;
|
||||
}
|
||||
|
||||
if (columnName === "Linked Rate") {
|
||||
if (!targetRow.isRate) {
|
||||
if (targetCell.classList.contains("selected-cell")) {
|
||||
document.selectedCostItems = document.selectedCostItems.filter(
|
||||
(costItem) => costItem !== targetRow.id
|
||||
);
|
||||
targetCell.classList.remove("selected-cell");
|
||||
} else {
|
||||
document.selectedCostItems.push(targetRow.id);
|
||||
targetCell.classList.add("selected-cell");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!document.selectedCostItems) {
|
||||
document.selectedCostItems = [];
|
||||
}
|
||||
document.selectedCostItems = [];
|
||||
addTableListeners(callbacks);
|
||||
table.listenersAdded = true;
|
||||
}
|
||||
|
||||
static addSettingsMenu() {
|
||||
@@ -154,7 +166,14 @@ export class CostUI {
|
||||
static createColumnSelector() {
|
||||
const columnSelector = document.createElement("div");
|
||||
columnSelector.id = "column-selector";
|
||||
const columns = ["ID", "Quantity", "Unit", "Classification", "Actions"];
|
||||
const columns = [
|
||||
"Identification",
|
||||
"Quantity",
|
||||
"Unit",
|
||||
"Classification",
|
||||
"Actions",
|
||||
"Linked Rate",
|
||||
];
|
||||
columns.forEach((column) => {
|
||||
const label = document.createElement("label");
|
||||
const input = document.createElement("input");
|
||||
@@ -181,6 +200,23 @@ export class CostUI {
|
||||
localStorage.setItem("columnPreferences", JSON.stringify(preferences));
|
||||
}
|
||||
|
||||
static getColumnPreferences() {
|
||||
const preferences = localStorage.getItem("columnPreferences");
|
||||
return preferences
|
||||
? JSON.parse(preferences)
|
||||
: {
|
||||
Identification: true,
|
||||
Name: true,
|
||||
Quantity: true,
|
||||
Unit: true,
|
||||
Cost: true,
|
||||
TotalCost: true,
|
||||
Classification: true,
|
||||
CostRate: true,
|
||||
Actions: true,
|
||||
};
|
||||
}
|
||||
|
||||
static updateColumnVisibility(preferences) {
|
||||
const tables = document.querySelectorAll("table[id^='cost-items-']");
|
||||
tables.forEach((table) => {
|
||||
@@ -352,6 +388,7 @@ export class CostUI {
|
||||
currency,
|
||||
callbacks,
|
||||
});
|
||||
this.addTableCallbacks(callbacks, table);
|
||||
if (costSchedule["cost_items"].length === 0) {
|
||||
const tr = document.createElement("tr");
|
||||
const td = document.createElement("td");
|
||||
@@ -439,11 +476,8 @@ export class CostUI {
|
||||
const cells = row.querySelectorAll("td, th");
|
||||
let rowText = [];
|
||||
|
||||
console.log();
|
||||
|
||||
if (CostUI.getCostItemRow(row.id)) {
|
||||
const costItem = CostUI.getCostItemRow(row.id);
|
||||
console.log(costItem.nestingLevel);
|
||||
rowText.push(costItem.nestingLevel);
|
||||
} else {
|
||||
rowText.push("Hierarchy Level");
|
||||
@@ -510,7 +544,6 @@ export class CostUI {
|
||||
|
||||
const callback = () => {
|
||||
tableWrapper.remove();
|
||||
CostUI.unhighlightElement("schedule-" + id);
|
||||
};
|
||||
let closeButton = CostUI.createCloseButton(callback);
|
||||
tableHeader.appendChild(text);
|
||||
@@ -520,26 +553,36 @@ export class CostUI {
|
||||
const table = document.createElement("table");
|
||||
table.id = "cost-items-" + id;
|
||||
const tbody = document.createElement("tbody");
|
||||
const columnHeaders = [
|
||||
{ name: "ID", visible: preferences.ID },
|
||||
let columnHeaders = [
|
||||
{ name: "Identification", visible: preferences.Identification },
|
||||
{ name: "Name", visible: preferences.Name },
|
||||
{ name: "Quantity", visible: preferences.Quantity },
|
||||
{ name: "Unit", visible: preferences.Unit },
|
||||
{ name: "Cost (" + currency + ")", visible: preferences.Cost },
|
||||
{ name: "Total Cost (" + currency + ")", visible: preferences.TotalCost },
|
||||
{ name: "Classification", visible: preferences.Classification },
|
||||
{ name: "Linked Rate", visible: preferences.CostRate },
|
||||
{ name: "Actions", visible: preferences.Actions },
|
||||
];
|
||||
|
||||
if (isScheduleOfRates) {
|
||||
columnHeaders.splice(2, 1);
|
||||
columnHeaders[3] = {
|
||||
name: "Rate (" + currency + ")",
|
||||
visible: preferences.Cost,
|
||||
};
|
||||
columnHeaders[4] = {
|
||||
name: "Total Rate (" + currency + ")",
|
||||
visible: preferences.TotalCost,
|
||||
};
|
||||
columnHeaders = columnHeaders.filter(
|
||||
(header) => header.name !== "Quantity"
|
||||
);
|
||||
columnHeaders = columnHeaders.filter(
|
||||
(header) => header.name !== "Linked Rate"
|
||||
);
|
||||
columnHeaders = columnHeaders.map((header) => {
|
||||
if (header.name === "Unit") {
|
||||
return { name: "Rate (" + currency + ")", visible: preferences.Cost };
|
||||
} else if (header.name === "Cost (" + currency + ")") {
|
||||
return {
|
||||
name: "Total Rate (" + currency + ")",
|
||||
visible: preferences.TotalCost,
|
||||
};
|
||||
}
|
||||
return header;
|
||||
});
|
||||
}
|
||||
const thead = document.createElement("thead");
|
||||
const tr = document.createElement("tr");
|
||||
@@ -597,9 +640,9 @@ export class CostUI {
|
||||
const identification = costItem.Identification
|
||||
? costItem.Identification
|
||||
: "XXX";
|
||||
if (preferences.ID) {
|
||||
if (preferences.Identification) {
|
||||
const idCell = CostUI.createTableCell(identification);
|
||||
idCell.setAttribute("data-column", "ID");
|
||||
idCell.setAttribute("data-column", "Identification");
|
||||
row.appendChild(idCell);
|
||||
}
|
||||
|
||||
@@ -633,7 +676,11 @@ export class CostUI {
|
||||
totalCostQuantityCell.classList.add("clickable-cell");
|
||||
}
|
||||
if (preferences.Unit) {
|
||||
const unitSymbolCell = CostUI.createTableCell(costItem.UnitSymbol);
|
||||
let symbol = costItem.UnitSymbol;
|
||||
if (symbol === "-" && costItem.UnitBasisUnitSymbol) {
|
||||
symbol = costItem.UnitBasisUnitSymbol;
|
||||
}
|
||||
const unitSymbolCell = CostUI.createTableCell(symbol);
|
||||
unitSymbolCell.setAttribute("data-column", "Unit");
|
||||
row.appendChild(unitSymbolCell);
|
||||
}
|
||||
@@ -670,8 +717,28 @@ export class CostUI {
|
||||
ClassificationCell.textContent = string_List;
|
||||
}
|
||||
|
||||
let costRateName = " ";
|
||||
let costItemRateId;
|
||||
if (costItem.CostRate.id !== null) {
|
||||
costItemRateId = costItem.CostRate.id;
|
||||
const costItemRateName = costItem.CostRate.Name;
|
||||
const costItemRateIdentification = costItem.CostRate.Identification;
|
||||
costRateName = costItemRateIdentification + " - " + costItemRateName;
|
||||
}
|
||||
|
||||
if (preferences.CostRate && !isScheduleOfRates) {
|
||||
const costRateCell = CostUI.createTableCell(costRateName);
|
||||
costRateCell.classList.add("rate-cell", "clickable-cell");
|
||||
costRateCell.setAttribute("data-column", "Linked Rate");
|
||||
row.appendChild(costRateCell);
|
||||
}
|
||||
|
||||
if (preferences.Actions) {
|
||||
const actionsCell = CostUI.costItemActions(costItem, callbacks);
|
||||
const actionsCell = CostUI.costItemActions(
|
||||
costItem,
|
||||
isScheduleOfRates,
|
||||
callbacks
|
||||
);
|
||||
actionsCell.setAttribute("data-column", "Actions");
|
||||
actionsCell.classList.add("actions-column");
|
||||
row.appendChild(actionsCell);
|
||||
@@ -946,7 +1013,12 @@ export class CostUI {
|
||||
return div;
|
||||
}
|
||||
|
||||
static costItemActions(costItem, callbacks) {
|
||||
static getSelectedCostItems() {
|
||||
const selectedCostItems = document.selectedCostItems || [];
|
||||
return selectedCostItems.map((item) => parseInt(item, 10));
|
||||
}
|
||||
|
||||
static costItemActions(costItem, isScheduleOfRates, callbacks) {
|
||||
const divFlex = document.createElement("div");
|
||||
divFlex.classList.add("row-container");
|
||||
const addCostItem = CostUI.addCostItemButton(
|
||||
@@ -965,12 +1037,22 @@ export class CostUI {
|
||||
costItem.id,
|
||||
callbacks.duplicateCostItem
|
||||
);
|
||||
|
||||
[addCostItem, duplicateButton, selectButton, deleteButton].forEach(
|
||||
(button) => {
|
||||
divFlex.appendChild(button);
|
||||
}
|
||||
const assignCostItemButton = CostUI.createButton(
|
||||
"Assign",
|
||||
"fa-solid fa-link"
|
||||
);
|
||||
assignCostItemButton.addEventListener("click", function () {
|
||||
callbacks.assignCostValues(costItem.id);
|
||||
});
|
||||
|
||||
let buttons = [addCostItem, duplicateButton, selectButton, deleteButton];
|
||||
if (isScheduleOfRates) {
|
||||
buttons.push(assignCostItemButton);
|
||||
}
|
||||
|
||||
buttons.forEach((button) => {
|
||||
divFlex.appendChild(button);
|
||||
});
|
||||
|
||||
const actionsCell = document.createElement("td");
|
||||
actionsCell.appendChild(divFlex);
|
||||
@@ -1107,12 +1189,13 @@ export class CostUI {
|
||||
return success;
|
||||
}
|
||||
|
||||
static createCard(id, title, mainContainer, callback) {
|
||||
static createCard(id, title, cardBody, callback) {
|
||||
const card = document.createElement("div");
|
||||
card.classList.add("card");
|
||||
card.id = "schedule-" + id;
|
||||
|
||||
const cardBody = document.createElement("div");
|
||||
const cardContainer = document.createElement("div");
|
||||
cardContainer.classList.add("card-body");
|
||||
cardBody.classList.add("card-body");
|
||||
|
||||
const cardTitle = CostUI.Text(
|
||||
@@ -1120,15 +1203,16 @@ export class CostUI {
|
||||
"fa-solid fa-money-bill-wave",
|
||||
"large"
|
||||
);
|
||||
cardTitle.classList.add("card-title");
|
||||
cardTitle.classList.add("card-header");
|
||||
|
||||
const cardButton = CostUI.createButton("Load", "fa-solid fa-arrows-rotate");
|
||||
cardButton.addEventListener("click", callback);
|
||||
cardTitle.appendChild(cardButton);
|
||||
|
||||
cardBody.appendChild(cardTitle);
|
||||
cardBody.appendChild(mainContainer);
|
||||
cardBody.appendChild(cardButton);
|
||||
card.appendChild(cardBody);
|
||||
card.appendChild(cardTitle);
|
||||
card.appendChild(cardContainer);
|
||||
|
||||
cardContainer.appendChild(cardBody);
|
||||
|
||||
return card;
|
||||
}
|
||||
@@ -2714,7 +2798,6 @@ export class CostUI {
|
||||
const classificationContainer = document.createElement("div");
|
||||
classificationContainer.classList.add("classification-container");
|
||||
classificationList.appendChild(classificationContainer);
|
||||
console.log(costClassifications);
|
||||
if (costClassifications.length === 0) {
|
||||
const text = "No classifications available";
|
||||
const noDataMessage = this.Text(
|
||||
@@ -2738,12 +2821,6 @@ export class CostUI {
|
||||
"click",
|
||||
function (event) {
|
||||
event.preventDefault();
|
||||
console.log(
|
||||
"Remove classification reference from",
|
||||
costItemId,
|
||||
"Classificaition Id",
|
||||
classification.id
|
||||
);
|
||||
callbacks.removeClassificationReference(
|
||||
costItemId,
|
||||
classification.id
|
||||
@@ -2819,7 +2896,6 @@ export class CostUI {
|
||||
formContainer.appendChild(container);
|
||||
|
||||
if (!classificationElements || classificationElements.length === 0) {
|
||||
console.log("No data available");
|
||||
const text =
|
||||
"No data available - please activate a classification file with BonsaiBIM ";
|
||||
const noDataMessage = this.Text(
|
||||
|
||||
@@ -667,11 +667,7 @@ class Cost(bonsai.core.tool.Cost):
|
||||
|
||||
@classmethod
|
||||
def get_cost_schedule(cls, cost_item: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
for rel in cost_item.HasAssignments or []:
|
||||
if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcCostSchedule"):
|
||||
return rel.RelatingControl
|
||||
for rel in cost_item.Nests or []:
|
||||
return cls.get_cost_schedule(rel.RelatingObject)
|
||||
return ifcopenshell.util.cost.get_cost_schedule(cost_item)
|
||||
|
||||
@classmethod
|
||||
def is_cost_schedule_active(cls, cost_schedule: ifcopenshell.entity_instance) -> bool:
|
||||
|
||||
@@ -594,6 +594,13 @@ class Web(bonsai.core.tool.Web):
|
||||
data_key="message",
|
||||
event="message",
|
||||
)
|
||||
if operator_data["type"] == "assignCostValues":
|
||||
cost_rate = ifc_file.by_id(operator_data["costRateId"])
|
||||
cost_schedule = tool.Cost.get_cost_schedule(ifc_file.by_id(operator_data["costItemIds"][0]))
|
||||
for cost_item_id in operator_data["costItemIds"]:
|
||||
cost_item = ifc_file.by_id(cost_item_id)
|
||||
tool.Ifc.run("cost.assign_cost_value", cost_item=cost_item, cost_rate=cost_rate)
|
||||
cls.load_cost_schedule_web_ui(cost_schedule)
|
||||
|
||||
@classmethod
|
||||
def load_cost_item_quantities_ui(cls, cost_item: ifcopenshell.entity_instance) -> None:
|
||||
|
||||
@@ -55,11 +55,17 @@ class ifc5D2json:
|
||||
self.extract_cost_item_quantities(cost_item, data)
|
||||
self.populate_cost_values(cost_item, data)
|
||||
self.populate_nesting_index(cost_item, data)
|
||||
cost_rate = ifcopenshell.util.cost.get_cost_rate(self.file, cost_item)
|
||||
data = {**data, **cost_item.get_info(recursive=True)}
|
||||
data["id"] = cost_item.id()
|
||||
data["IsNestedBy"] = []
|
||||
data["IsSum"] = self.check_if_cost_item_is_sum(cost_item)
|
||||
data["Classification"] = self.get_cost_classifications(cost_item)
|
||||
data["CostRate"] = {
|
||||
"id": cost_rate.id() if cost_rate else None,
|
||||
"Identification": cost_rate.Identification if cost_rate else None,
|
||||
"Name": cost_rate.Name if cost_rate else None,
|
||||
}
|
||||
json_data.append(data)
|
||||
for rel in cost_item.IsNestedBy or []:
|
||||
for sub_cost in rel.RelatedObjects:
|
||||
|
||||
@@ -307,6 +307,34 @@ def get_product_quantity_names(elements: list[ifcopenshell.entity_instance]) ->
|
||||
return [n for n in names if n != "id"]
|
||||
|
||||
|
||||
def get_cost_schedule(cost_item: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
|
||||
"""Returns the cost schedule of a cost item.
|
||||
"""
|
||||
for rel in cost_item.HasAssignments or []:
|
||||
if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcCostSchedule"):
|
||||
return rel.RelatingControl
|
||||
for rel in cost_item.Nests or []:
|
||||
return get_cost_schedule(rel.RelatingObject)
|
||||
|
||||
|
||||
def get_cost_rate(
|
||||
file: ifcopenshell_wrapper.file, cost_item: ifcopenshell.entity_instance
|
||||
) -> Optional[ifcopenshell.entity_instance]:
|
||||
"""Returns the cost rate of a cost item.
|
||||
|
||||
"""
|
||||
# There is no direct relationship between a cost item and a cost rate in IFC, so we need to infer it, based on the assumption that cost_rate.CostValues == cost_item.CostValues.
|
||||
if get_cost_schedule(cost_item).PredefinedType == "SCHEDULEOFRATES":
|
||||
return None # Cost item is already a cost rate
|
||||
if cost_item.CostValues:
|
||||
potential_rates = file.get_inverse(cost_item.CostValues[0])
|
||||
for potential_rate in potential_rates:
|
||||
schedule = get_cost_schedule(potential_rate)
|
||||
if schedule.PredefinedType == "SCHEDULEOFRATES":
|
||||
return potential_rate
|
||||
return None
|
||||
|
||||
|
||||
class CostValueUnserialiser:
|
||||
def parse(self, formula: str):
|
||||
l = lark.Lark(
|
||||
|
||||
Reference in New Issue
Block a user