diff --git a/Binner/Binner.Web/ClientApp/public/locales/en/translation.json b/Binner/Binner.Web/ClientApp/public/locales/en/translation.json
index adcfce10..7b195dcf 100644
--- a/Binner/Binner.Web/ClientApp/public/locales/en/translation.json
+++ b/Binner/Binner.Web/ClientApp/public/locales/en/translation.json
@@ -506,6 +506,7 @@
"footprintName": "Footprint Name",
"extensionValue1": "Extension Value 1",
"extensionValue2": "Extension Value 2",
+ "partId": "Part Id",
"ok": "Ok",
"popup": {
"lowStock": "Quantities below this value will indicate the part is low on stock.",
@@ -803,7 +804,8 @@
"symbolName": "KiCad Symbol Name",
"footprintName": "KiCad Footprint Name",
"extensionValue1": "Extension Value 1",
- "extensionValue2": "Extension Value 2"
+ "extensionValue2": "Extension Value 2",
+ "partId": "Part Id"
},
"message": {
"noPartsAdded": "No parts added.",
diff --git a/Binner/Binner.Web/ClientApp/src/components/PartsGrid2.css b/Binner/Binner.Web/ClientApp/src/components/PartsGrid2.css
index 242a8609..693ff7f1 100644
--- a/Binner/Binner.Web/ClientApp/src/components/PartsGrid2.css
+++ b/Binner/Binner.Web/ClientApp/src/components/PartsGrid2.css
@@ -100,4 +100,8 @@
.MuiSwitch-root:hover {
scale: 0.9 !important;
+}
+
+.Mui-TableHeadCell-Content-Labels .Mui-TableHeadCell-Content-Wrapper {
+ padding-right: 2px;
}
\ No newline at end of file
diff --git a/Binner/Binner.Web/ClientApp/src/components/PartsGrid2Memoized.js b/Binner/Binner.Web/ClientApp/src/components/PartsGrid2Memoized.js
index 05ef3ca7..c51ad6dd 100644
--- a/Binner/Binner.Web/ClientApp/src/components/PartsGrid2Memoized.js
+++ b/Binner/Binner.Web/ClientApp/src/components/PartsGrid2Memoized.js
@@ -182,6 +182,8 @@ export default function PartsGrid2Memoized(props) {
switch (columnName) {
case "partNumber":
return 180;
+ case "partId":
+ return 180;
case "quantity":
return 140;
case "lowStockThreshold":
@@ -268,6 +270,8 @@ export default function PartsGrid2Memoized(props) {
switch(columnName){
case 'partNumber':
return {...def, Cell: ({row}) => ( {row.original.partNumber})};
+ case 'partId':
+ return { ...def, Cell: ({ row }) => ( {row.original.partId}) };
case 'manufacturerPartNumber':
return {...def, Cell: ({row}) => ( {row.original[columnName]})};
case 'description':
@@ -489,7 +493,7 @@ PartsGrid2Memoized.propTypes = {
PartsGrid2Memoized.defaultProps = {
loading: true,
- columns: "partNumber,quantity,lowStockThreshold,manufacturerPartNumber,description,partType,packageType,mountingType,location,binNumber,binNumber2,cost,digikeyPartNumber,mouserPartNumber,arrowPartNumber,datasheetUrl,print,delete,symbolName,footprintName,extensionValue1,extensionValue2",
+ columns: "partNumber,partId,quantity,lowStockThreshold,manufacturerPartNumber,description,partType,packageType,mountingType,location,binNumber,binNumber2,cost,digikeyPartNumber,mouserPartNumber,arrowPartNumber,datasheetUrl,print,delete,symbolName,footprintName,extensionValue1,extensionValue2",
defaultVisibleColumns: "partNumber,quantity,manufacturerPartNumber,description,partType,location,binNumber,binNumber2,cost,datasheetUrl,print,delete",
page: 1,
totalPages: 1,
diff --git a/Binner/Binner.Web/ClientApp/src/pages/Inventory.js b/Binner/Binner.Web/ClientApp/src/pages/Inventory.js
index a555348a..584b1807 100644
--- a/Binner/Binner.Web/ClientApp/src/pages/Inventory.js
+++ b/Binner/Binner.Web/ClientApp/src/pages/Inventory.js
@@ -134,7 +134,16 @@ export function Inventory(props) {
partTypesRef.current = partTypes;
useEffect(() => {
- const partNumberStr = props.params.partNumber;
+ const partNumberRaw = props.params.partNumber;
+ let partNumberStr = partNumberRaw?.trim();
+ let partId = 0;
+ if (partNumberRaw?.includes(':')) {
+ const parts = partNumberRaw.split(':');
+ if (parts.length >= 1)
+ partNumberStr = parts[0].trim();
+ if (parts.length >= 2)
+ partId = parseInt(parts[1].trim());
+ }
const newIsEditing = partNumberStr?.length > 0;
setIsEditing(newIsEditing);
const fetchData = async () => {
@@ -143,7 +152,7 @@ export function Inventory(props) {
await fetchPartTypes();
await fetchRecentRows();
if (partNumberStr) {
- var loadedPart = await fetchPart(partNumberStr);
+ var loadedPart = await fetchPart(partNumberStr, partId);
if (newIsEditing) setInputPartNumber(partNumberStr);
setInputPartNumber(partNumberStr);
await fetchPartMetadataAndInventory(partNumberStr, loadedPart || part);
@@ -528,12 +537,16 @@ export function Inventory(props) {
setPart(part);
};
- const fetchPart = async (partNumber) => {
+ const fetchPart = async (partNumber, partId) => {
Inventory.partAbortController.abort();
Inventory.partAbortController = new AbortController();
setLoadingPart(true);
try {
- const response = await fetchApi(`api/part?partNumber=${encodeURIComponent(partNumber.trim())}`, {
+ let query = `partNumber=${encodeURIComponent(partNumber.trim())}`;
+ const validPartId = typeof partId === "number" ? partId : partId && parseInt(partId.trim());
+ if (validPartId > 0)
+ query += `&partId=${partId}`;
+ const response = await fetchApi(`api/part?${query}`, {
signal: Inventory.partAbortController.signal
});
const { data } = response;
@@ -851,7 +864,7 @@ export function Inventory(props) {
e.stopPropagation();
if (systemSettings.printer.printMode === 0) {
// direct print
- await fetchApi(`api/part/print?partNumber=${encodeURIComponent(part.partNumber.trim())}&generateImageOnly=false`, { method: "POST" });
+ await fetchApi(`api/part/print?partNumber=${encodeURIComponent(part.partNumber.trim())}&partId=${part.partId}&generateImageOnly=false`, { method: "POST" });
} else {
window.print();
}
@@ -881,8 +894,11 @@ export function Inventory(props) {
const handleRecentPartClick = async (e, part) => {
setPart(part);
- props.history(`/inventory/${encodeURIComponent(part.partNumber)}`);
- await fetchPart(part.partNumber);
+ if (part.partId)
+ props.history(`/inventory/${encodeURIComponent(part.partNumber)}:${part.partId}`);
+ else
+ props.history(`/inventory/${encodeURIComponent(part.partNumber)}`);
+ await fetchPart(part.partNumber, part.partId);
};
const handleSaveScannedParts = async (e, scannedParts) => {
@@ -1477,7 +1493,7 @@ export function Inventory(props) {
{isEditing ? part.partNumber : t('page.inventory.addtitle', "Add Inventory")}
- {part.partNumber && }
+ {part.partNumber && }
{!isEditing &&
diff --git a/Binner/Binner.Web/ClientApp/src/pages/LowInventory.js b/Binner/Binner.Web/ClientApp/src/pages/LowInventory.js
index 082743ea..17185c55 100644
--- a/Binner/Binner.Web/ClientApp/src/pages/LowInventory.js
+++ b/Binner/Binner.Web/ClientApp/src/pages/LowInventory.js
@@ -86,7 +86,10 @@ export function LowInventory (props) {
};
const handlePartClick = (e, part) => {
- props.history(`/inventory/${encodeURIComponent(part.partNumber)}`);
+ if (part.partId)
+ props.history(`/inventory/${encodeURIComponent(part.partNumber)}:${part.partId}`);
+ else
+ props.history(`/inventory/${encodeURIComponent(part.partNumber)}`);
};
const removeFilter = (e, filterName, filterValue) => {
diff --git a/Binner/Binner.Web/ClientApp/src/pages/Search.js b/Binner/Binner.Web/ClientApp/src/pages/Search.js
index 3a6ac8bc..e5815bb3 100644
--- a/Binner/Binner.Web/ClientApp/src/pages/Search.js
+++ b/Binner/Binner.Web/ClientApp/src/pages/Search.js
@@ -178,7 +178,10 @@ export function Search(props) {
}, [filterBy, filterByValue, keyword]);
const handlePartClick = (e, part) => {
- props.history(`/inventory/${encodeURIComponent(part.partNumber)}`);
+ if (part.partId)
+ props.history(`/inventory/${encodeURIComponent(part.partNumber)}:${part.partId}`);
+ else
+ props.history(`/inventory/${encodeURIComponent(part.partNumber)}`);
};
const handleNextPage = async (e, page) => {
diff --git a/Binner/Binner.Web/Controllers/PartController.cs b/Binner/Binner.Web/Controllers/PartController.cs
index 3b5be88b..c790d2b5 100644
--- a/Binner/Binner.Web/Controllers/PartController.cs
+++ b/Binner/Binner.Web/Controllers/PartController.cs
@@ -67,7 +67,7 @@ public async Task
GetAsync([FromQuery] GetPartRequest request)
{
if (string.IsNullOrEmpty(request.PartNumber))
return BadRequest($"No part number specified!");
- var response = await _partService.GetPartWithStoredFilesAsync(request.PartNumber);
+ var response = await _partService.GetPartWithStoredFilesAsync(request);
if (response.Part == null) return NotFound();
var partResponse = Mapper.Map(response.Part);
partResponse.StoredFiles = response.StoredFiles;
@@ -335,7 +335,7 @@ public async Task SearchAsync([FromQuery] string keywords, [FromQ
if (exactMatch)
{
// search by exact part name match
- var part = await _partService.GetPartAsync(keywords);
+ var part = await _partService.GetPartAsync(new GetPartRequest { PartNumber = keywords });
if (part == null) return NotFound();
var partTypes = await _partService.GetPartTypesAsync();
@@ -599,7 +599,7 @@ public async Task PrintPartAsync([FromQuery] PrintPartRequest req
try
{
if (string.IsNullOrEmpty(request.PartNumber)) return BadRequest("No part number specified.");
- var part = await _partService.GetPartAsync(request.PartNumber);
+ var part = await _partService.GetPartAsync(new GetPartRequest { PartNumber = request.PartNumber, PartId = request.PartId });
if (part == null) return NotFound();
if (await _printService.HasPartLabelTemplateAsync())
@@ -651,7 +651,7 @@ public async Task PreviewPrintPartAsync([FromQuery] PrintPartRequ
System.Threading.Thread.CurrentPrincipal = new TokenPrincipal(userContext, request.Token);
if (string.IsNullOrEmpty(request.PartNumber)) return BadRequest("No part number specified.");
- var part = await _partService.GetPartAsync(request.PartNumber);
+ var part = await _partService.GetPartAsync(new GetPartRequest { PartNumber = request.PartNumber, PartId = request.PartId });
var stream = new MemoryStream();
if (await _printService.HasPartLabelTemplateAsync())
diff --git a/Binner/Library/Binner.Common/Services/IPartService.cs b/Binner/Library/Binner.Common/Services/IPartService.cs
index 3490c64f..e573b7b7 100644
--- a/Binner/Library/Binner.Common/Services/IPartService.cs
+++ b/Binner/Library/Binner.Common/Services/IPartService.cs
@@ -48,16 +48,16 @@ public interface IPartService
///
/// Get a part by part number
///
- ///
+ ///
///
- Task GetPartAsync(string partNumber);
+ Task GetPartAsync(GetPartRequest request);
///
/// Get a part with its associated stored files
///
- ///
+ ///
///
- Task<(Part? Part, ICollection StoredFiles)> GetPartWithStoredFilesAsync(string partNumber);
+ Task<(Part? Part, ICollection StoredFiles)> GetPartWithStoredFilesAsync(GetPartRequest request);
///
/// Get all parts
diff --git a/Binner/Library/Binner.Common/Services/PartService.cs b/Binner/Library/Binner.Common/Services/PartService.cs
index 708ba5a7..06a9699d 100644
--- a/Binner/Library/Binner.Common/Services/PartService.cs
+++ b/Binner/Library/Binner.Common/Services/PartService.cs
@@ -89,15 +89,18 @@ public async Task> GetLowStockAsync(PaginatedRequest req
return await _storageProvider.GetLowStockAsync(request, _requestContext.GetUserContext());
}
- public async Task GetPartAsync(string partNumber)
+ public async Task GetPartAsync(GetPartRequest request)
{
- return await _storageProvider.GetPartAsync(partNumber, _requestContext.GetUserContext());
+ if (request.PartId > 0)
+ return await _storageProvider.GetPartAsync(request.PartId, _requestContext.GetUserContext());
+ else
+ return await _storageProvider.GetPartAsync(request.PartNumber ?? string.Empty, _requestContext.GetUserContext());
}
- public async Task<(Part? Part, ICollection StoredFiles)> GetPartWithStoredFilesAsync(string partNumber)
+ public async Task<(Part? Part, ICollection StoredFiles)> GetPartWithStoredFilesAsync(GetPartRequest request)
{
var userContext = _requestContext.GetUserContext();
- var partEntity = await _storageProvider.GetPartAsync(partNumber, userContext);
+ var partEntity = await GetPartAsync(request);
var storedFiles = new List();
if (partEntity != null)
{
@@ -1082,7 +1085,7 @@ private CommonPart MouserOrderLineToCommonPart(OrderHistoryLine? orderLine, stri
var datasheetUrls = new List>();
// fetch part if it's in inventory
- var inventoryPart = await GetPartAsync(partNumber);
+ var inventoryPart = await GetPartAsync(new GetPartRequest { PartNumber = partNumber });
if (inventoryPart != null && !string.IsNullOrEmpty(inventoryPart.DatasheetUrl))
datasheetUrls.Add(new NameValuePair(inventoryPart.ManufacturerPartNumber ?? string.Empty, new DatasheetSource($"https://{_configuration.ResourceSource}/{MissingDatasheetCoverName}", inventoryPart.DatasheetUrl, inventoryPart.ManufacturerPartNumber ?? string.Empty, inventoryPart.Description ?? string.Empty, inventoryPart.Manufacturer ?? string.Empty)));
if (inventoryPart != null)
diff --git a/Binner/Library/Binner.Model/Requests/GetPartRequest.cs b/Binner/Library/Binner.Model/Requests/GetPartRequest.cs
index 8081abe0..75bc1fd8 100644
--- a/Binner/Library/Binner.Model/Requests/GetPartRequest.cs
+++ b/Binner/Library/Binner.Model/Requests/GetPartRequest.cs
@@ -6,5 +6,10 @@ public class GetPartRequest
/// The main part number
///
public string? PartNumber { get; set; }
+
+ ///
+ /// Optional part id
+ ///
+ public long PartId { get; set; }
}
}
diff --git a/Binner/Library/Binner.Model/Requests/PrintPartRequest.cs b/Binner/Library/Binner.Model/Requests/PrintPartRequest.cs
index 9ba7e97c..629dda6e 100644
--- a/Binner/Library/Binner.Model/Requests/PrintPartRequest.cs
+++ b/Binner/Library/Binner.Model/Requests/PrintPartRequest.cs
@@ -6,7 +6,12 @@ public class PrintPartRequest : IImagesToken
/// The main part number
///
public string? PartNumber { get; set; }
-
+
+ ///
+ /// Optional part id
+ ///
+ public long PartId { get; set; }
+
///
/// True to generate image only
///