From ebaea33030110fc41b54be50b6a852c6aacad7d0 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 3 May 2023 17:49:57 -0700 Subject: [PATCH] Fixes a UI issue with Producing PCB's, sometimes the UI data wasn't getting updated properly and it showed PCB could not be produced even though it had enough parts. Also fixes an issue with migrating old data where there are duplicate partType entries which fails the upgrade process. Thanks to @interbiznw for reporting both of these! --- .../public/locales/en/translation.json | 3 ++- .../src/components/ProducePcbModal.js | 18 +++++++++--------- Binner/Binner.Web/ClientApp/src/pages/Bom.js | 16 ++++++++++------ .../StorageProviders/MigrationHandler.cs | 16 ++++++++++------ 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/Binner/Binner.Web/ClientApp/public/locales/en/translation.json b/Binner/Binner.Web/ClientApp/public/locales/en/translation.json index 240ab3da..bf08deae 100644 --- a/Binner/Binner.Web/ClientApp/public/locales/en/translation.json +++ b/Binner/Binner.Web/ClientApp/public/locales/en/translation.json @@ -739,7 +739,8 @@ "addedPart": "Added part {{partNumber}}!", "failedSavePart": "Failed to update, check Part Type and Mounting Type", "noPartMetadata": "No part metadata found!", - "scanPartExists": "Part exists in inventory. You can edit it's details or remove it from your scan." + "scanPartExists": "Part exists in inventory. You can edit it's details or remove it from your scan.", + "noOutOfStockParts": "No out of stock parts." }, "confirm": { "deleteProject": "Are you sure you want to delete this project and your entire BOM?", diff --git a/Binner/Binner.Web/ClientApp/src/components/ProducePcbModal.js b/Binner/Binner.Web/ClientApp/src/components/ProducePcbModal.js index 9bbd0708..99cd52ea 100644 --- a/Binner/Binner.Web/ClientApp/src/components/ProducePcbModal.js +++ b/Binner/Binner.Web/ClientApp/src/components/ProducePcbModal.js @@ -1,5 +1,5 @@ import React, { useState, useEffect, useCallback } from "react"; -import { useTranslation, Trans } from 'react-i18next'; +import { useTranslation } from 'react-i18next'; import { Button, Form, Modal, Image, Header, Popup, Input, Table, Icon } from "semantic-ui-react"; import PropTypes from "prop-types"; import NumberPicker from "./NumberPicker"; @@ -15,7 +15,7 @@ export function ProducePcbModal(props) { const [project, setProject] = useState({ parts:[], pcbs: []}); const [pcbOptions, setPcbOptions] = useState([]); - const canProducePcb = (pcb) => { + const canProducePcb = (project, pcb = null) => { const formQuantity = parseInt(form.quantity) || 1; if (pcb && pcb.pcbId > 0) { const pcbParts = _.filter(project.parts, p => p.pcbId === pcb.pcbId); @@ -30,12 +30,12 @@ export function ProducePcbModal(props) { const createPcbOptions = (project) => { const options = [ { key: -1, text: t('comp.producePcbModal.options.all', "All"), description: t('comp.producePcbModal.options.allDescription', "Produce the entire BOM"), value: -1 }, - { key: 0, text: t('comp.producePcbModal.options.unassociated', "Unassociated"), description: t('comp.producePcbModal.options.unassociatedDescription', "Produce parts not associated to a PCB"), value: 0, icon: !canProducePcb() && "warning circle", disabled: getProducibleUnassociatedCount(project.parts) === 0 }, + { key: 0, text: t('comp.producePcbModal.options.unassociated', "Unassociated"), description: t('comp.producePcbModal.options.unassociatedDescription', "Produce parts not associated to a PCB"), value: 0, icon: !canProducePcb(project) && "warning circle", disabled: getProducibleUnassociatedCount(project.parts) === 0 }, ]; if (project && project.pcbs && project.pcbs.length > 0) { for(let i = 0; i < project.pcbs.length; i++) { - options.push({ key: i + 1, text: project.pcbs[i].name, description: project.pcbs[i].description, value: project.pcbs[i].pcbId, icon: !canProducePcb(project.pcbs[i]) && "warning circle", disabled: !canProducePcb(project.pcbs[i])}); + options.push({ key: i + 1, text: project.pcbs[i].name, description: project.pcbs[i].description, value: project.pcbs[i].pcbId, icon: !canProducePcb(project, project.pcbs[i]) && "warning circle", disabled: !canProducePcb(project, project.pcbs[i])}); } } return options; @@ -108,14 +108,14 @@ export function ProducePcbModal(props) { // ensure we don't pass any pcb's to produce that don't have enough parts const validPcbsToProcess = []; for(let i = 0; i < pcbsToProcess.length; i++){ - if (canProducePcb(pcbsToProcess[i])){ + if (canProducePcb(project, pcbsToProcess[i])){ validPcbsToProcess.push(pcbsToProcess[i]); } } // only process unassociated if parts are available let processUnassociated = false; - if ((form.pcbs.includes(0) || form.pcbs.includes(-1)) && canProducePcb()) + if ((form.pcbs.includes(0) || form.pcbs.includes(-1)) && canProducePcb(project)) processUnassociated = true; props.onSubmit(e, { ...form, @@ -200,7 +200,7 @@ export function ProducePcbModal(props) { {getPcbsToDisplay(project).map((p, key) => ( - + {p.name} {p.pcbId && @@ -208,7 +208,7 @@ export function ProducePcbModal(props) { wide content={t('comp.producePcbModal.popup.serialNumber', "The next PCB will have it's serial number started at this value.")} trigger={ - + } /> } @@ -216,7 +216,7 @@ export function ProducePcbModal(props) { {p.pcbId > 0 ? getProduciblePcbCount(project.parts, p).count : getProducibleUnassociatedCount(project.parts)} {p.pcbId > 0 ? _.filter(project.parts, x => x.pcbId === p.pcbId).length : _.filter(project.parts, x => x.pcbId === null).length} {getTotalPartsOutOfStock(p.pcbId && p.pcbId > 0 ? p : null)} - {!canProducePcb(p) && {t('message.notEnoughParts', "Not enough parts")}} + {!canProducePcb(project, p) && (p.pcbId > 0 ? _.filter(project.parts, x => x.pcbId === p.pcbId).length : _.filter(project.parts, x => x.pcbId === null).length > 0) && {t('message.notEnoughParts', "Not enough parts")}} ))} diff --git a/Binner/Binner.Web/ClientApp/src/pages/Bom.js b/Binner/Binner.Web/ClientApp/src/pages/Bom.js index fc3a964e..84e8ad01 100644 --- a/Binner/Binner.Web/ClientApp/src/pages/Bom.js +++ b/Binner/Binner.Web/ClientApp/src/pages/Bom.js @@ -895,12 +895,16 @@ export function Bom(props) { ) : ( - {t("message.noPartsAdded", "No parts added.")} -
-
- - {t("button.addFirstPart", "Add your first part!")} - + {filterInStock ? t("message.noOutOfStockParts", "No out of stock parts.") : + <> + {t("message.noPartsAdded", "No parts added.")} +
+
+ + {t("button.addFirstPart", "Add your first part!")} + + + }
)} diff --git a/Binner/Library/Binner.Common/StorageProviders/MigrationHandler.cs b/Binner/Library/Binner.Common/StorageProviders/MigrationHandler.cs index 811a636c..6a47cea1 100644 --- a/Binner/Library/Binner.Common/StorageProviders/MigrationHandler.cs +++ b/Binner/Library/Binner.Common/StorageProviders/MigrationHandler.cs @@ -450,15 +450,19 @@ private bool ImportBinnerDb(Binner.Model.Common.IBinnerDb binnerDb, BinnerContex IsAdmin = true }); - foreach (var e in db.PartTypes) + var partTypes = db.PartTypes.GroupBy(x => x.PartTypeId); + foreach (var e in partTypes) { + var firstInGroup = e.FirstOrDefault(); + if (e.Count() > 1) + _logger.Warn($"Found a duplicate part type record '{firstInGroup.PartTypeId}'! Filtering out"); context.PartTypes.Add(new Data.Model.PartType { - PartTypeId = e.PartTypeId, - ParentPartTypeId = e.ParentPartTypeId, - Name = e.Name, - DateCreatedUtc = e.DateCreatedUtc, - DateModifiedUtc = e.DateCreatedUtc, + PartTypeId = firstInGroup.PartTypeId, + ParentPartTypeId = firstInGroup.ParentPartTypeId, + Name = firstInGroup.Name, + DateCreatedUtc = firstInGroup.DateCreatedUtc, + DateModifiedUtc = firstInGroup.DateCreatedUtc, UserId = userId }); }