From 9e64a232998a1a45aca177d7b7e9993a05e7843d Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 7 Apr 2023 19:57:08 -0700 Subject: [PATCH] adds currency support with overriding of currency type per part. (still needs user settings ability) --- Binner/Binner.Web/Binner.Web.csproj | 2 +- .../public/locales/de/translation.json | 520 ++++++++++++++- .../public/locales/en/translation.json | 1 + .../public/locales/es/translation.json | 595 +++++++++++++++++- .../public/locales/fr/translation.json | 595 +++++++++++++++++- .../public/locales/zh/translation.json | 595 +++++++++++++++++- .../Binner.Web/ClientApp/src/common/Utils.js | 17 +- .../ClientApp/src/common/currency.js | 13 + .../ClientApp/src/components/PartsGrid.js | 3 +- Binner/Binner.Web/ClientApp/src/custom.css | 21 +- Binner/Binner.Web/ClientApp/src/i18n.js | 10 +- .../ClientApp/src/layouts/Header.js | 25 +- Binner/Binner.Web/ClientApp/src/pages/Bom.js | 53 +- Binner/Binner.Web/ClientApp/src/pages/Home.js | 25 +- .../ClientApp/src/pages/Inventory.css | 18 + .../ClientApp/src/pages/Inventory.js | 25 +- .../ClientApp/src/pages/OrderImport.js | 2 +- .../Binner.Web/Controllers/PartController.cs | 3 +- .../Binner.Common/Binner.Common.csproj | 2 +- .../Models/Requests/AddBomPartRequest.cs | 10 + .../Binner.Common/Models/Requests/PartBase.cs | 5 + .../Models/Requests/UpdateBomPartRequest.cs | 15 +- .../Models/Responses/PartResponse.cs | 5 + .../Models/Responses/ProjectPart.cs | 10 + .../Binner.Common/Services/ProjectService.cs | 22 +- .../BinnerFileStorageProvider.cs | 146 ++--- 26 files changed, 2590 insertions(+), 148 deletions(-) create mode 100644 Binner/Binner.Web/ClientApp/src/common/currency.js diff --git a/Binner/Binner.Web/Binner.Web.csproj b/Binner/Binner.Web/Binner.Web.csproj index 3e393763..41be7f4f 100644 --- a/Binner/Binner.Web/Binner.Web.csproj +++ b/Binner/Binner.Web/Binner.Web.csproj @@ -1,7 +1,7 @@  - 1.0.36 + 1.0.37 net7.0 win10-x64 win10-x64;linux-arm;linux-arm64;linux-x64;osx.10.12-x64;ubuntu.14.04-x64 diff --git a/Binner/Binner.Web/ClientApp/public/locales/de/translation.json b/Binner/Binner.Web/ClientApp/public/locales/de/translation.json index ca3b76b9..a911851d 100644 --- a/Binner/Binner.Web/ClientApp/public/locales/de/translation.json +++ b/Binner/Binner.Web/ClientApp/public/locales/de/translation.json @@ -1,5 +1,69 @@ { "page": { + "home": { + "title": "Dashboard", + "description": "Binner is an inventory management app for makers, hobbyists and professionals.", + "addInventory": "Add Inventory", + "searchInventory": "Search Inventory", + "bom": "BOM", + "datasheets": "Datasheets", + "viewLowStock": "View Low Stock", + "partTypes": "Part Types", + "importExport": "Import/Export", + "printLabels": "Print Labels", + "tools": "Tools", + "settings": "Settings", + "yourOverview": "Your Overview", + "lowStock": "Low Stock", + "parts": "Parts", + "uniqueParts": "Unique Parts", + "value": "Value", + "projects": "Projects" + }, + "search": { + "title":"Inventory", + "search":"Seach" + }, + "inventory": { + "addtitle": "Add Inventory", + "edittitle": "Edit Inventory", + "datasheets": "Datasheets", + "pinout": "Pinout", + "circuits": "Circuits", + "productImages": "Product Images", + "referenceDesigns": "Reference Designs", + "bulkScan": "Bulk Scan", + "startScanning": "Start scanning parts...", + "recentlyAdded": "Recently Added", + "partMetadata": "Part Metadata", + "privatePartInfo": "Private Part Information", + "suppliers": "Suppliers", + "addKeyword": "Add Keyword", + "chooseAlternatePart": "Choose alternate part ({{count}})", + "placeholder": { + "location": "Home lab", + "keywords": "op amp", + "binNumber": "IC Components 2", + "binNumber2": "14" + }, + "popup": { + "bulkAddParts": "Bulk add parts using a barcode scanner", + "quantity": "Use the mousewheel and CTRL/ALT to change step size", + "lowStock": "Alert when the quantity gets below this value", + "location": "A custom value for identifying the parts location", + "binNumber": "A custom value for identifying the parts location", + "rememberLastSelection": "Enable this toggle to remember the last selected values of: <1>Part Type, Mounting Type, Quantity, Low Stock, Project, Location, Bin Number, Bin Number 2", + "clear": "Clear the form to default values", + "alternateParts": "Choose a different part to extract metadata information from. By default, Binner will give you the most relevant part and with the highest quantity available.", + "mustAddPart": "You must save the part before adding custom suppliers to it.", + "addSupplier": "Add a manual supplier entry", + "deleteLocalFile": "Delete this local file" + } + }, + "lowInventory": { + "title": "Low Inventory", + "description": "Use this page to reorder parts you are low on.<1/>You can define a custom <3>Low Stock value per part in your inventory." + }, "bom": { "bc": { "home": "Home", @@ -7,7 +71,7 @@ "bom": "BOM" }, "header": { - "title": "Bill of Materials GERMAN", + "title": "Bill of Materials", "description": "Manage your BOM by creating PCB(s) and adding your parts." }, "notEnoughPartsToProducePcb": "You do not have enough parts to produce this PCB.", @@ -21,7 +85,281 @@ "header": { "description": "Bill of Materials, or BOM allows you to manage inventory quantities per project. You can reduce quantities for each PCB you produce, check which parts you need to buy more of and analyze costs.

Choose or create the project to manage BOM for.
" } - } + }, + "partTypes": { + "title": "Part Types", + "description": "Part Types allow you to separate your parts by type. <1>Parent types allow for unlimited part type hierarchy.<3 />For example: OpAmps may be a sub-type of IC's, so OpAmp's parent type is IC." + }, + "exportData": { + "title": "Import/Export Data", + "description": "Import or Export your Binner database to a human-readable format.", + "uploadNote": "Drag a document to upload, or click to select files", + "acceptedFileTypes": "Accepted file types: \"*.sql, *.xls, *.xlsx, *.csv\"", + "importResult": "Import Result", + "totalRowsImported": "Total Rows Imported", + "totalProjectsImported": "Projects Imported", + "totalPartTypesImported": "Part Types Imported", + "totalPartsImported": "Parts Imported" + }, + "tools": { + "title": "Tools", + "description": "Binner includes a suite of free utilities common to daily life in electrical engineering.", + "resistorColorCodeCalc": "Resistor Color Code Calculator", + "ohmsLawCalc": "Ohms Law Calculator", + "voltageDividerCalc": "Voltage Divider Calculator", + "barcodeScanner": "Barcode Scanner" + }, + "barcodeScanner": { + "title": "Barcode Scanner", + "description": "Test your barcode scanner to see what values it outputs.", + "detected": "Detected", + "waitingForInput": "Waiting for input..." + }, + "settings": { + "title": "Settings", + "description": "Configure your integrations, printer configuration, as well as label part templates. <1 /> Additional help can be found on the <3>Wiki", + "confirm": { + "mustAuthenticateHeader": "Must Authenticate", + "mustAuthenticate": "External Api is requesting that you authenticate first. You will be redirected back after authenticating with the external provider." + }, + "integrations": "Integrations", + "integrationsDescription": "To integrate with DigiKey, Mouser, Arrow or Octopart/Nexar API's you must obtain API keys for each service you wish to use. <1 /> Adding integrations will greatly enhance your experience.", + "swarm": "Swarm", + "swarmDescription": "Swarm is a free API service provided by <1>Binner's cloud service that contains part metadata from many aggregate sources. It is the primary source of part, media and datasheet information. Registering for your own API Keys will give you higher request limits and can be obtained at <3>https://binner.io/swarm", + "swarmSupport": "Swarm Support", + "digikey": "DigiKey", + "digikeySupport": "DigiKey Support", + "mouser": "Mouser", + "mouserSupport": "Mouser Support", + "searchApiKey": "Search Api Key", + "ordersApiKey": "Orders Api Key", + "cartApiKey": "Cart Api Key", + "arrow": "Arrow", + "arrowSupport": "Arrow Support", + "octopartNexar": "Octopart/Nexar", + "octopartNexarSupport": "Octopart/Nexar Support", + "printerConfiguration": "Printer Configuration", + "printerConfigDescription": "Configure your printer name as it shows up in your environment (Windows Printers or CUPS Printer Name)", + "printerName": "Printer Name", + "partLabelSource": "Part Label Source", + "partLabelName": "Part Label Name", + "partLabelTemplate": "Part Label Template", + "partLabelTemplateDescription": "Part labels are printed according to this template.", + "lineX": "Line {{number}}", + "identifierX": "Identifier {{number}}" + }, + "datasheet": { + "title": "Datasheet Search" + }, + "orderImport": { + "title": "Order Import", + "enterOrderNumber": "Enter your order number for the supplier.", + "webOrderNum": "Web Order #", + "salesOrderNum": "Sales Order #", + "orderNum": "Order Number", + "instructions": "For <1>DigiKey orders, this is the <3>Sales Order #.<4 />For <6>Mouser orders, this is the <8>Web Order #.<10 />For <12>Arrow orders, this is the <14>Order Number.", + "mouserNote": "<0>Note: Mouser only supports Web Order # so make sure when importing that you are using the Web Order # and <2>not the Sales Order #", + "arrowNote": "<0>Note: Arrow requires that you first request access to their Order API by sending them an email. See <2>Arrow Order Api" + }, + "printLabels": { + "title": "Print Labels", + "description": "Print custom multi-line labels for your storage bins.<1 />Print history is kept so you can reuse templates for your labels.", + "printHistory": "Print History", + "label": { + "labelType": "Label Type", + "paperSource": "Paper Source", + "labelNum": "Label #", + "text": "Text", + "fontSize": "FontSize", + "alignment": "Alignment", + "topMargin": "Top Margin", + "leftMargin": "Left Margin", + "font": "Font", + "isBarcode": "Is Barcode", + "center": "Center" + } + }, + "project": { + "title": "Edit Project", + "description": "Projects are used as part of your BOM, allowing you to associate parts to multiple PCBs.", + "pcbs": "PCBs", + "confirm": { + "deleteProjectHeader": "Delete Project", + "deletePcbHeader": "Delete Pcb" + }, + "placeholder": { + "name": "My Project", + "location": "New York" + }, + "popup": { + "name": "Enter the name of your project/BOM", + "description": "Enter a description that summarizes your project", + "location": "Your project's location <1>(optional)", + "color": "Associate a color with this project for easy identification" + } + }, + "tool": { + "ohmsLaw": { + "title": "Ohms Law Calculator", + "description": "Ohms Law explains the relationship between voltage, current and resistance. Input any 2 values to calculate the other 2 values.", + "voltage": "Voltage", + "current": "Current", + "resistance": "Resistance", + "power": "Power" + }, + "resistanceColorCalc": { + "title": "Resistor Color Code Calculator", + "4band": "4 Band", + "5band": "5 Band", + "6band": "6 Band", + "numOfBands": "Choose the number of bands", + "chooseBands": "Choose bands...", + "resistance": "Resistance" + }, + "voltDividerCalc": { + "title": "Voltage Divider Calculator", + "description": "A voltage divider uses 2 resistors to reduce a voltage to a fraction of its input voltage.", + "voltageInput": "Voltage Input", + "resistor": "Resistor", + "outputVoltage": "Output Voltage" + } + } + }, + "bc": { + "home": "Home", + "boms": "BOMs", + "bom": "BOM", + "editProject": "Edit Project", + "tools": "Tools", + "ohmsLaw": "Ohms Law Calculator", + "resistanceColorCalc": "Resistor Color Code Calculator", + "voltDividerCalc": "Voltage Divider Calculator" + }, + "comp": { + "navBar": { + "search": "Search", + "help": "Help", + "home": "Home", + "addInventory": "Add Inventory", + "orderImport": "Order Import" + }, + "partsGrid": { + "recordsPerPage": "records per page", + "parts": "Parts", + "part": "Part", + "quantity": "Quantity", + "lowStock": "Low Stock", + "mfrPart": "Manufacturer Part", + "description": "Description", + "location": "Location", + "binNumber": "Bin Number", + "binNumber2": "Bin Number 2", + "cost": "Cost", + "digikeyPart": "DigiKey Part", + "mouserPart": "Mouser Part", + "arrowPart": "Arrow Part", + "datasheet": "Datasheet", + "noResults": "No results.", + "ok": "Ok", + "popup": { + "lowStock": "Quantities below this value will indicate the part is low on stock.", + "quantity": "The quantity of parts currently in stock." + }, + "confirm": { + "deletePart": "Are you sure you want to delete part {{partNumber}}?" + }, + "error": { + "failedSave": "Error saving part {{partNumber}} - {{statusText}}" + } + }, + "addBomPartModal": { + "title": "BOM Management", + "description": "Add a part to your BOM, optionally associating it with a particular PCB.", + "none": "None", + "confirmHeader": "Add Part", + "confirmAddUnassociated": "You have not selected a part from your inventory.<1/>Are you sure you want to add this part without associating it to a part in your inventory?<3/><4/><5>Note: You will still be able to manage it's quantity if you choose to proceed, but it will not appear in your inventory.", + "selectPcb": "Select PCB", + "choosePcb": "Choose PCB", + "popup": { + "selectPcb": "Select the pcb you would like to add parts to. If you choose not to select a PCB, the part will be added to your BOM without PCB associations.", + "quantity": "Enter the quantity of this part required to produce a single PCB.", + "referenceIds": "Enter a custom Reference Id you can use for identifying this part.<1/>Examples: <3>Optoisolator1, <5>Capacitor Array", + "schematicReferenceIds": "Enter one or more Schematic Reference Ids you can use for identifying this part on the PCB silkscreen.<1/>Examples: <3>D1, <5>C2, <7>Q1", + "notes": "Enter any custom notes for this part.", + "partNumber": "Search for a part in your inventory", + "customDescription": "Enter your own custom description for this part." + } + }, + "addPcbModal": { + "title": "BOM Management", + "description": "Adding a PCB allows you to associate your parts with a specific PCB, and even multiple PCBs within a project.", + "header": "Add PCB", + "popup": { + "name": "Enter the name of your pcb board or module", + "description": "Enter a description describing what your pcb does. (<1>optional)", + "quantity": "Enter a quantity (multiplier) of PCB's produced each time you create a PCB. This should normally be 1, unless you require several copies of the same PCB for producing your BOM project.<1/><2/><3>Example: An audio amplifier may require 2 of the same PCB's, one for each left/right channel each time you produce the entire assembly.", + "cost": "The cost to produce a single PCB board (without components). If using quantity, only specify the cost for a single board as quantity will be taken into consideration.", + "serialNumberFormat": "Enter your serial number format as a string. The left-most portion of the string will be incremented by 1 each time you produce a PCB. (<1>optional)<2/>Example: <4>SN00000000" + }, + "placeholder": { + "name": "Main Board or module name", + "description": "Description of pcb" + } + }, + "producePcbModal": { + "title": "BOM Management", + "description": "", + "header": "Produce Pcb", + "nextSerialNumber": "Next Serial Number", + "maxQty": "Max Qty", + "options": { + "all": "All", + "allDescription": "Produce the entire BOM", + "unassociated": "Unassociated", + "unassociatedDescription": "Produce parts not associated to a PCB" + }, + "popup": { + "pcbs": "Select the pcb(s) you would like to produce. If you don't define PCB's, choose Unassociated or All.", + "quantity": "Enter the quantity of PCBs you are producing.", + "nextSerialNumber": "The next serial number assigned to the board", + "maxQty": "The maximum number of boards you can produce", + "parts": "The number of parts on the board", + "outOfStock": "The number of parts on the board that are out of stock", + "serialNumber": "The next PCB will have it's serial number started at this value." + }, + "label": { + "pcbs": "Select PCB(s)" + }, + "placeholder": { + "pcbs": "Choose PCB(s) to produce" + } + }, + "chooseAlternatePartModal": { + "title": "Matching Parts" + }, + "lineTemplate": { + "label": { + "content": "Content", + "font": "Font", + "fontSize": "Font Size", + "fontColor": "Font Color", + "textPosition": "Text Position", + "upperCaseText": "UpperCase Text", + "lowerCaseText": "LowerCase Text", + "barcode": "Barcode", + "marginLeft": "Margin Left", + "marginTop": "Margin Top", + "rotateDegrees": "Rotate Degrees" + }, + "popup": { + "content": "Template can reference any part field, example: {partNumber}, {description}, {manufacturer}, {location}, {binNumber}, {cost} etc. See <1>Wiki for all available tags.", + "autoSize": "Text size will be automatically determined.", + "upperCaseText": "Render the text as all upper-case characters.", + "lowerCaseText": "Render the text as all lower-case characters.", + "barcode": "Render the Content value encoded as a barcode.", + "rotateDegrees": "Rotate the text in degrees. Example: 90" + } + } }, "button": { "addBomProject": "Add BOM Project", @@ -33,7 +371,28 @@ "removePart": "Remove Part", "removeXParts": "Remove ({{checkboxesChecked}}) Parts", "addFirstPart": "Add your first part!", - "all": "All" + "all": "All", + "addPartType": "Add Part Type", + "manualAdd": "Manual Add", + "back": "Back", + "import": "Import", + "export": "Export", + "testApi": "Test Api", + "forgetCredentials": "Forget Credentials", + "viewDatasheet": "View Datasheet", + "search": "Search", + "clear": "Clear", + "reset": "Reset", + "importParts": "Import Parts", + "preview": "Preview", + "print": "Print", + "load": "Load", + "addLine": "Add Line", + "add": "Add", + "cancel": "Cancel", + "produce": "Produce", + "delete": "Delete", + "visit": "Visit" }, "label": { "name": "Name", @@ -44,6 +403,7 @@ "parts": "Parts", "pcb": "Pcb", "pcbs": "Pcbs", + "delete": "Delete", "deleteProject": "Delete Project", "editProject": "Edit Project", "lastModified": "Last modified", @@ -51,42 +411,136 @@ "outOfStock": "Out of Stock", "totalParts": "Total Parts", "producible": "Producible", + "part": "Part", "partNumber": "Part Number", + "partNumberShort": "Part#", + "supplierPart": "Supplier Part", "mfrPart": "Mfr Part", + "manufacturer": "Manufacturer", "partType": "Part Type", + "mountingType": "Mounting Type", "cost": "Cost", "quantity": "Quantity", + "quantityShort": "Qty", "leadTime": "Lead Time", "referenceIds": "Reference Id(s)", "schematicReferenceIds": "Schematic Reference Id(s)", "customDescription": "Custom Description", "note": "Note", - "recordsPerPage": "records per page" + "notes": "Notes", + "recordsPerPage": "records per page", + "parent": "Parent", + "partsCount": "Parts Count", + "systemType": "System Type", + "resistors": "Resistors", + "hideEmptyTypes": "Hide Empty Types", + "error": "Error", + "errors": "Errors", + "or": "Or", + "status": "Status", + "success": "Success", + "failed": "Failed", + "warnings": "Warnings", + "apiKey": "Api Key", + "apiUrl": "Api Url", + "clientId": "Client Id", + "clientSecret": "Client Secret", + "timeout": "Timeout", + "postbackUrl": "Postback Url", + "username": "Username", + "password": "Password", + "website": "Website", + "datasheet": "Datasheet", + "package": "Package", + "importQuestion": "Import?", + "image": "Image", + "customerId": "Customer Id", + "orderAmount": "Order Amount", + "orderDate": "Order Date", + "trackingNumber": "Tracking Number", + "unspecified": "Unspecified", + "viewTracking": "View Tracking", + "preview": "Preview", + "serialNumberFormat": "Serial Number Format", + "family": "Family", + "source": "Source", + "qtyAvail": "QTY Avail.", + "keywords": "Keywords", + "apiEndpoint": "Api Endpoint", + "lastSerialNumber": "Last Serial Number", + "lowStock": "Low Stock", + "binNumber": "Bin Number", + "binNumber2": "Bin Number 2", + "manufacturerPart": "Manufacturer Part", + "packageType": "Package Type", + "primaryDatasheetUrl": "Primary Datasheet Url", + "productUrl": "Product Url", + "digikeyPartNumber": "DigiKey Part Number", + "mouserPartNumber": "Mouser Part Number", + "arrowPartNumber": "Arrow Part Number", + "minimumOrderQuantity": "Minimum Order Quantity", + "supplier": "Supplier", + "supplierPartNumber": "Supplier Part Number", + "quantityAvailable": "Quantity Available", + "rememberLastSelection": "Remember last selection", + "origin": "Origin", + "supplierType": "Supplier Type" }, "message": { - "noPartsAdded": "No parts added." + "noPartsAdded": "No parts added.", + "noChildPartTypes": "There are no child part types.", + "noResults": "No Results", + "loadingOrder": "Loading order# {{order}}", + "noMatchingResults":"No matching results.", + "notEnoughParts": "Not enough parts", + "noPartInfo": "No part information is available for '{{partNumber}}'. You are subscribed to updates and will be automatically updated when the part is indexed." }, "confirm": { "deleteProject": "Are you sure you want to delete this project and your entire BOM?", - "removeBomParts": "Are you sure you want to remove these {{quantity}} part(s) from your BOM?", - "permanent": "This action is permanent and cannot be recovered.", + "removeBomParts": "Are you sure you want to remove these <1>{{quantity}} part(s) from your BOM?", + "deletePcb": "Are you sure you want to delete this PCB and it's parts from your BOM?", + "deletePartType": "Are you sure you want to delete part type <1>{{name}}?", + "deletePart": "Are you sure you want to delete part <1>{{name}}?", + "deleteLocalFile": "Are you sure you want to delete this local file named <1>{{name}}?", + "permanent": "This action is <1>permanent and cannot be recovered.", "header": { - "removeBomPart": "Remove Part from BOM" - } + "removeBomPart": "Remove Part from BOM", + "deleteFile": "Delete File", + "deletePart": "Delete Part" + } }, "error": { "deleteProjectFailed": "Failed to delete project!", "failedToRemoveBomParts": "Failed to remove parts from BOM!", "projectNotFound": "Could not find project named {{projectName}}", "failedSaveProject": "Failed to save project change!", + "failedSavePcb": "Failed to save pcb!", + "failedDeleteProject": "Failed to remove project!", + "failedSavePartType": "Failed to save Part Type!", + "failedDeletePcb": "Failed to remove pcb!", "noPartSelected": "No part selected!", "failedAddPart": "Failed to add part!", "failedAddPcb": "Failed to add pcb!", - "failedBomExport": "BOM export failed!" + "failedBomExport": "BOM export failed!", + "failedDeletedPartType": "Failed to delete part type {{name}}", + "failedAddedPartType": "Failed to add part type {{name}}", + "failedClearedCredentials": "Failed to clear cached credentials for {{apiName}}", + "testFailed": "Test failed", + "invalidOrder": "Hmmm, that doesn't look like a valid order number!", + "invalid2dBarcode": "Hmmm, I don't recognize that 2D barcode!" }, "success": { "producedPcbs": "{{quantity}} PCB's were produced!", - "bomExported": "BOM exported successfully!" + "bomExported": "BOM exported successfully!", + "savedPartType": "Saved part type {{name}}", + "deletedPartType": "Deleted part type {{name}}", + "barcodeTypeReceived": "Barcode type {{type}} received", + "systemSettingsSaved": "System settings were saved.", + "clearedCredentials": "Successfully cleared cached credentials for {{apiName}}", + "testPassed": "Test passed", + "partsImported": "{{count}} parts were imported!", + "deletedProject": "Project was deleted!", + "deletedPcb": "Pcb was deleted!" }, "popup": { "bom": { @@ -96,19 +550,47 @@ "addPcb": "Add a PCB to this project", "producePcb": "Reduce inventory quantities when a PCB is assembled", "filterOutOfStock": "Select to only show out of stock parts", - "pcb": "Indicates which PCB your part is assigned to. A PCB assignment is optional, all unassigned parts will appear in the All tab.", + "pcb": "Indicates which PCB your part is assigned to. A PCB assignment is optional, all unassigned parts will appear in the <1>All tab.", "quantity": "The quantity of parts required for a single PCB", "inventoryQuantity": "The quantity of parts currently in inventory", - "referenceId": "Your custom reference Id(s) you can use for identifying this part.", - "schematicReferenceId": "Your custom schematic reference Id(s) that identify the part on the PCB silkscreen.
Examples: D1, C2, Q1", + "referenceId": "Your custom <1 />reference Id(s) you can use for identifying this part.", + "schematicReferenceId": "Your custom <1 />schematic reference Id(s) that identify the part on the PCB silkscreen. <3/>Examples: <5>D1, <7>C2, <9>Q1", "displayAll": "Displays all parts including parts not associated with a PCB.", - "unassociatedPartName": "Edit the name of your unassociated part", - "bomQuantity": "Edit the BOM quantity required", - "quantityAvailable": "Edit the quantity available in your inventory", - "editReferenceId": "Edit your custom reference Id(s) you can use for identifying this part.", - "editSchematicReferenceId": "Edit your custom schematic reference Id(s) that identify the part on the PCB silkscreen.
Examples: D1, C2, Q1", + "unassociatedPartName": "Edit the name of your unassociated <1 /> part", + "bomQuantity": "Edit the <1 />BOM quantity required", + "bomCost": "Edit the part cost", + "quantityAvailable": "Edit the quantity available in your <1 />inventory", + "editReferenceId": "Edit your custom <1 />reference Id(s) you can use for identifying this part.", + "editSchematicReferenceId": "Edit your custom <1 />schematic reference Id(s) that identify the part on the PCB silkscreen. <3 />Examples: <5>D1, <7>C2, <9>Q1", "customDescription": "Provide a custom description", "customNote": "Provide a custom note" } + }, + "footer": { + "version": "Version", + "promo": "Try <1>Binner Cloud Free" + }, + "notification": { + "versionBanner": { + "newVersion": "A new version of Binner <1>{{version}} is available!", + "releaseNotes": "Release Notes", + "view": "View", + "skip": "Skip", + "close": "Close" + } + }, + "color": { + "black": "Black", + "brown": "Brown", + "red": "Red", + "orange": "Orange", + "yellow": "Yellow", + "green": "Green", + "blue": "Blue", + "violet": "Violet", + "grey": "Grey", + "white": "White", + "gold": "Gold", + "silver": "Silver" } } \ No newline at end of file diff --git a/Binner/Binner.Web/ClientApp/public/locales/en/translation.json b/Binner/Binner.Web/ClientApp/public/locales/en/translation.json index 1110a263..a911851d 100644 --- a/Binner/Binner.Web/ClientApp/public/locales/en/translation.json +++ b/Binner/Binner.Web/ClientApp/public/locales/en/translation.json @@ -558,6 +558,7 @@ "displayAll": "Displays all parts including parts not associated with a PCB.", "unassociatedPartName": "Edit the name of your unassociated <1 /> part", "bomQuantity": "Edit the <1 />BOM quantity required", + "bomCost": "Edit the part cost", "quantityAvailable": "Edit the quantity available in your <1 />inventory", "editReferenceId": "Edit your custom <1 />reference Id(s) you can use for identifying this part.", "editSchematicReferenceId": "Edit your custom <1 />schematic reference Id(s) that identify the part on the PCB silkscreen. <3 />Examples: <5>D1, <7>C2, <9>Q1", diff --git a/Binner/Binner.Web/ClientApp/public/locales/es/translation.json b/Binner/Binner.Web/ClientApp/public/locales/es/translation.json index c1860fa3..a911851d 100644 --- a/Binner/Binner.Web/ClientApp/public/locales/es/translation.json +++ b/Binner/Binner.Web/ClientApp/public/locales/es/translation.json @@ -1,3 +1,596 @@ { - + "page": { + "home": { + "title": "Dashboard", + "description": "Binner is an inventory management app for makers, hobbyists and professionals.", + "addInventory": "Add Inventory", + "searchInventory": "Search Inventory", + "bom": "BOM", + "datasheets": "Datasheets", + "viewLowStock": "View Low Stock", + "partTypes": "Part Types", + "importExport": "Import/Export", + "printLabels": "Print Labels", + "tools": "Tools", + "settings": "Settings", + "yourOverview": "Your Overview", + "lowStock": "Low Stock", + "parts": "Parts", + "uniqueParts": "Unique Parts", + "value": "Value", + "projects": "Projects" + }, + "search": { + "title":"Inventory", + "search":"Seach" + }, + "inventory": { + "addtitle": "Add Inventory", + "edittitle": "Edit Inventory", + "datasheets": "Datasheets", + "pinout": "Pinout", + "circuits": "Circuits", + "productImages": "Product Images", + "referenceDesigns": "Reference Designs", + "bulkScan": "Bulk Scan", + "startScanning": "Start scanning parts...", + "recentlyAdded": "Recently Added", + "partMetadata": "Part Metadata", + "privatePartInfo": "Private Part Information", + "suppliers": "Suppliers", + "addKeyword": "Add Keyword", + "chooseAlternatePart": "Choose alternate part ({{count}})", + "placeholder": { + "location": "Home lab", + "keywords": "op amp", + "binNumber": "IC Components 2", + "binNumber2": "14" + }, + "popup": { + "bulkAddParts": "Bulk add parts using a barcode scanner", + "quantity": "Use the mousewheel and CTRL/ALT to change step size", + "lowStock": "Alert when the quantity gets below this value", + "location": "A custom value for identifying the parts location", + "binNumber": "A custom value for identifying the parts location", + "rememberLastSelection": "Enable this toggle to remember the last selected values of: <1>Part Type, Mounting Type, Quantity, Low Stock, Project, Location, Bin Number, Bin Number 2", + "clear": "Clear the form to default values", + "alternateParts": "Choose a different part to extract metadata information from. By default, Binner will give you the most relevant part and with the highest quantity available.", + "mustAddPart": "You must save the part before adding custom suppliers to it.", + "addSupplier": "Add a manual supplier entry", + "deleteLocalFile": "Delete this local file" + } + }, + "lowInventory": { + "title": "Low Inventory", + "description": "Use this page to reorder parts you are low on.<1/>You can define a custom <3>Low Stock value per part in your inventory." + }, + "bom": { + "bc": { + "home": "Home", + "boms": "BOMs", + "bom": "BOM" + }, + "header": { + "title": "Bill of Materials", + "description": "Manage your BOM by creating PCB(s) and adding your parts." + }, + "notEnoughPartsToProducePcb": "You do not have enough parts to produce this PCB.", + "notEnoughPartsToProduceBom": "You do not have enough parts to produce your entire BOM.", + "noPartsAssigned": "Assign parts to get a count of how many times you can produce this PCB.", + "pcbProduceCount": "You can produce this PCB {{count}} times with your current inventory.", + "bomProduceCount": "You can produce your entire BOM {{count}} times with your current inventory.", + "lowestPcb": "{{name}} is the lowest on inventory." + }, + "boms": { + "header": { + "description": "Bill of Materials, or BOM allows you to manage inventory quantities per project. You can reduce quantities for each PCB you produce, check which parts you need to buy more of and analyze costs.

Choose or create the project to manage BOM for.
" + } + }, + "partTypes": { + "title": "Part Types", + "description": "Part Types allow you to separate your parts by type. <1>Parent types allow for unlimited part type hierarchy.<3 />For example: OpAmps may be a sub-type of IC's, so OpAmp's parent type is IC." + }, + "exportData": { + "title": "Import/Export Data", + "description": "Import or Export your Binner database to a human-readable format.", + "uploadNote": "Drag a document to upload, or click to select files", + "acceptedFileTypes": "Accepted file types: \"*.sql, *.xls, *.xlsx, *.csv\"", + "importResult": "Import Result", + "totalRowsImported": "Total Rows Imported", + "totalProjectsImported": "Projects Imported", + "totalPartTypesImported": "Part Types Imported", + "totalPartsImported": "Parts Imported" + }, + "tools": { + "title": "Tools", + "description": "Binner includes a suite of free utilities common to daily life in electrical engineering.", + "resistorColorCodeCalc": "Resistor Color Code Calculator", + "ohmsLawCalc": "Ohms Law Calculator", + "voltageDividerCalc": "Voltage Divider Calculator", + "barcodeScanner": "Barcode Scanner" + }, + "barcodeScanner": { + "title": "Barcode Scanner", + "description": "Test your barcode scanner to see what values it outputs.", + "detected": "Detected", + "waitingForInput": "Waiting for input..." + }, + "settings": { + "title": "Settings", + "description": "Configure your integrations, printer configuration, as well as label part templates. <1 /> Additional help can be found on the <3>Wiki", + "confirm": { + "mustAuthenticateHeader": "Must Authenticate", + "mustAuthenticate": "External Api is requesting that you authenticate first. You will be redirected back after authenticating with the external provider." + }, + "integrations": "Integrations", + "integrationsDescription": "To integrate with DigiKey, Mouser, Arrow or Octopart/Nexar API's you must obtain API keys for each service you wish to use. <1 /> Adding integrations will greatly enhance your experience.", + "swarm": "Swarm", + "swarmDescription": "Swarm is a free API service provided by <1>Binner's cloud service that contains part metadata from many aggregate sources. It is the primary source of part, media and datasheet information. Registering for your own API Keys will give you higher request limits and can be obtained at <3>https://binner.io/swarm", + "swarmSupport": "Swarm Support", + "digikey": "DigiKey", + "digikeySupport": "DigiKey Support", + "mouser": "Mouser", + "mouserSupport": "Mouser Support", + "searchApiKey": "Search Api Key", + "ordersApiKey": "Orders Api Key", + "cartApiKey": "Cart Api Key", + "arrow": "Arrow", + "arrowSupport": "Arrow Support", + "octopartNexar": "Octopart/Nexar", + "octopartNexarSupport": "Octopart/Nexar Support", + "printerConfiguration": "Printer Configuration", + "printerConfigDescription": "Configure your printer name as it shows up in your environment (Windows Printers or CUPS Printer Name)", + "printerName": "Printer Name", + "partLabelSource": "Part Label Source", + "partLabelName": "Part Label Name", + "partLabelTemplate": "Part Label Template", + "partLabelTemplateDescription": "Part labels are printed according to this template.", + "lineX": "Line {{number}}", + "identifierX": "Identifier {{number}}" + }, + "datasheet": { + "title": "Datasheet Search" + }, + "orderImport": { + "title": "Order Import", + "enterOrderNumber": "Enter your order number for the supplier.", + "webOrderNum": "Web Order #", + "salesOrderNum": "Sales Order #", + "orderNum": "Order Number", + "instructions": "For <1>DigiKey orders, this is the <3>Sales Order #.<4 />For <6>Mouser orders, this is the <8>Web Order #.<10 />For <12>Arrow orders, this is the <14>Order Number.", + "mouserNote": "<0>Note: Mouser only supports Web Order # so make sure when importing that you are using the Web Order # and <2>not the Sales Order #", + "arrowNote": "<0>Note: Arrow requires that you first request access to their Order API by sending them an email. See <2>Arrow Order Api" + }, + "printLabels": { + "title": "Print Labels", + "description": "Print custom multi-line labels for your storage bins.<1 />Print history is kept so you can reuse templates for your labels.", + "printHistory": "Print History", + "label": { + "labelType": "Label Type", + "paperSource": "Paper Source", + "labelNum": "Label #", + "text": "Text", + "fontSize": "FontSize", + "alignment": "Alignment", + "topMargin": "Top Margin", + "leftMargin": "Left Margin", + "font": "Font", + "isBarcode": "Is Barcode", + "center": "Center" + } + }, + "project": { + "title": "Edit Project", + "description": "Projects are used as part of your BOM, allowing you to associate parts to multiple PCBs.", + "pcbs": "PCBs", + "confirm": { + "deleteProjectHeader": "Delete Project", + "deletePcbHeader": "Delete Pcb" + }, + "placeholder": { + "name": "My Project", + "location": "New York" + }, + "popup": { + "name": "Enter the name of your project/BOM", + "description": "Enter a description that summarizes your project", + "location": "Your project's location <1>(optional)", + "color": "Associate a color with this project for easy identification" + } + }, + "tool": { + "ohmsLaw": { + "title": "Ohms Law Calculator", + "description": "Ohms Law explains the relationship between voltage, current and resistance. Input any 2 values to calculate the other 2 values.", + "voltage": "Voltage", + "current": "Current", + "resistance": "Resistance", + "power": "Power" + }, + "resistanceColorCalc": { + "title": "Resistor Color Code Calculator", + "4band": "4 Band", + "5band": "5 Band", + "6band": "6 Band", + "numOfBands": "Choose the number of bands", + "chooseBands": "Choose bands...", + "resistance": "Resistance" + }, + "voltDividerCalc": { + "title": "Voltage Divider Calculator", + "description": "A voltage divider uses 2 resistors to reduce a voltage to a fraction of its input voltage.", + "voltageInput": "Voltage Input", + "resistor": "Resistor", + "outputVoltage": "Output Voltage" + } + } + }, + "bc": { + "home": "Home", + "boms": "BOMs", + "bom": "BOM", + "editProject": "Edit Project", + "tools": "Tools", + "ohmsLaw": "Ohms Law Calculator", + "resistanceColorCalc": "Resistor Color Code Calculator", + "voltDividerCalc": "Voltage Divider Calculator" + }, + "comp": { + "navBar": { + "search": "Search", + "help": "Help", + "home": "Home", + "addInventory": "Add Inventory", + "orderImport": "Order Import" + }, + "partsGrid": { + "recordsPerPage": "records per page", + "parts": "Parts", + "part": "Part", + "quantity": "Quantity", + "lowStock": "Low Stock", + "mfrPart": "Manufacturer Part", + "description": "Description", + "location": "Location", + "binNumber": "Bin Number", + "binNumber2": "Bin Number 2", + "cost": "Cost", + "digikeyPart": "DigiKey Part", + "mouserPart": "Mouser Part", + "arrowPart": "Arrow Part", + "datasheet": "Datasheet", + "noResults": "No results.", + "ok": "Ok", + "popup": { + "lowStock": "Quantities below this value will indicate the part is low on stock.", + "quantity": "The quantity of parts currently in stock." + }, + "confirm": { + "deletePart": "Are you sure you want to delete part {{partNumber}}?" + }, + "error": { + "failedSave": "Error saving part {{partNumber}} - {{statusText}}" + } + }, + "addBomPartModal": { + "title": "BOM Management", + "description": "Add a part to your BOM, optionally associating it with a particular PCB.", + "none": "None", + "confirmHeader": "Add Part", + "confirmAddUnassociated": "You have not selected a part from your inventory.<1/>Are you sure you want to add this part without associating it to a part in your inventory?<3/><4/><5>Note: You will still be able to manage it's quantity if you choose to proceed, but it will not appear in your inventory.", + "selectPcb": "Select PCB", + "choosePcb": "Choose PCB", + "popup": { + "selectPcb": "Select the pcb you would like to add parts to. If you choose not to select a PCB, the part will be added to your BOM without PCB associations.", + "quantity": "Enter the quantity of this part required to produce a single PCB.", + "referenceIds": "Enter a custom Reference Id you can use for identifying this part.<1/>Examples: <3>Optoisolator1, <5>Capacitor Array", + "schematicReferenceIds": "Enter one or more Schematic Reference Ids you can use for identifying this part on the PCB silkscreen.<1/>Examples: <3>D1, <5>C2, <7>Q1", + "notes": "Enter any custom notes for this part.", + "partNumber": "Search for a part in your inventory", + "customDescription": "Enter your own custom description for this part." + } + }, + "addPcbModal": { + "title": "BOM Management", + "description": "Adding a PCB allows you to associate your parts with a specific PCB, and even multiple PCBs within a project.", + "header": "Add PCB", + "popup": { + "name": "Enter the name of your pcb board or module", + "description": "Enter a description describing what your pcb does. (<1>optional)", + "quantity": "Enter a quantity (multiplier) of PCB's produced each time you create a PCB. This should normally be 1, unless you require several copies of the same PCB for producing your BOM project.<1/><2/><3>Example: An audio amplifier may require 2 of the same PCB's, one for each left/right channel each time you produce the entire assembly.", + "cost": "The cost to produce a single PCB board (without components). If using quantity, only specify the cost for a single board as quantity will be taken into consideration.", + "serialNumberFormat": "Enter your serial number format as a string. The left-most portion of the string will be incremented by 1 each time you produce a PCB. (<1>optional)<2/>Example: <4>SN00000000" + }, + "placeholder": { + "name": "Main Board or module name", + "description": "Description of pcb" + } + }, + "producePcbModal": { + "title": "BOM Management", + "description": "", + "header": "Produce Pcb", + "nextSerialNumber": "Next Serial Number", + "maxQty": "Max Qty", + "options": { + "all": "All", + "allDescription": "Produce the entire BOM", + "unassociated": "Unassociated", + "unassociatedDescription": "Produce parts not associated to a PCB" + }, + "popup": { + "pcbs": "Select the pcb(s) you would like to produce. If you don't define PCB's, choose Unassociated or All.", + "quantity": "Enter the quantity of PCBs you are producing.", + "nextSerialNumber": "The next serial number assigned to the board", + "maxQty": "The maximum number of boards you can produce", + "parts": "The number of parts on the board", + "outOfStock": "The number of parts on the board that are out of stock", + "serialNumber": "The next PCB will have it's serial number started at this value." + }, + "label": { + "pcbs": "Select PCB(s)" + }, + "placeholder": { + "pcbs": "Choose PCB(s) to produce" + } + }, + "chooseAlternatePartModal": { + "title": "Matching Parts" + }, + "lineTemplate": { + "label": { + "content": "Content", + "font": "Font", + "fontSize": "Font Size", + "fontColor": "Font Color", + "textPosition": "Text Position", + "upperCaseText": "UpperCase Text", + "lowerCaseText": "LowerCase Text", + "barcode": "Barcode", + "marginLeft": "Margin Left", + "marginTop": "Margin Top", + "rotateDegrees": "Rotate Degrees" + }, + "popup": { + "content": "Template can reference any part field, example: {partNumber}, {description}, {manufacturer}, {location}, {binNumber}, {cost} etc. See <1>Wiki for all available tags.", + "autoSize": "Text size will be automatically determined.", + "upperCaseText": "Render the text as all upper-case characters.", + "lowerCaseText": "Render the text as all lower-case characters.", + "barcode": "Render the Content value encoded as a barcode.", + "rotateDegrees": "Rotate the text in degrees. Example: 90" + } + } + }, + "button": { + "addBomProject": "Add BOM Project", + "addPart": "Add Part", + "save": "Save", + "download": "Download", + "addPcb": "Add PCB", + "producePcb": "Produce PCB", + "removePart": "Remove Part", + "removeXParts": "Remove ({{checkboxesChecked}}) Parts", + "addFirstPart": "Add your first part!", + "all": "All", + "addPartType": "Add Part Type", + "manualAdd": "Manual Add", + "back": "Back", + "import": "Import", + "export": "Export", + "testApi": "Test Api", + "forgetCredentials": "Forget Credentials", + "viewDatasheet": "View Datasheet", + "search": "Search", + "clear": "Clear", + "reset": "Reset", + "importParts": "Import Parts", + "preview": "Preview", + "print": "Print", + "load": "Load", + "addLine": "Add Line", + "add": "Add", + "cancel": "Cancel", + "produce": "Produce", + "delete": "Delete", + "visit": "Visit" + }, + "label": { + "name": "Name", + "description": "Description", + "location": "Location", + "color": "Color", + "project": "Project", + "parts": "Parts", + "pcb": "Pcb", + "pcbs": "Pcbs", + "delete": "Delete", + "deleteProject": "Delete Project", + "editProject": "Edit Project", + "lastModified": "Last modified", + "inStock": "In Stock", + "outOfStock": "Out of Stock", + "totalParts": "Total Parts", + "producible": "Producible", + "part": "Part", + "partNumber": "Part Number", + "partNumberShort": "Part#", + "supplierPart": "Supplier Part", + "mfrPart": "Mfr Part", + "manufacturer": "Manufacturer", + "partType": "Part Type", + "mountingType": "Mounting Type", + "cost": "Cost", + "quantity": "Quantity", + "quantityShort": "Qty", + "leadTime": "Lead Time", + "referenceIds": "Reference Id(s)", + "schematicReferenceIds": "Schematic Reference Id(s)", + "customDescription": "Custom Description", + "note": "Note", + "notes": "Notes", + "recordsPerPage": "records per page", + "parent": "Parent", + "partsCount": "Parts Count", + "systemType": "System Type", + "resistors": "Resistors", + "hideEmptyTypes": "Hide Empty Types", + "error": "Error", + "errors": "Errors", + "or": "Or", + "status": "Status", + "success": "Success", + "failed": "Failed", + "warnings": "Warnings", + "apiKey": "Api Key", + "apiUrl": "Api Url", + "clientId": "Client Id", + "clientSecret": "Client Secret", + "timeout": "Timeout", + "postbackUrl": "Postback Url", + "username": "Username", + "password": "Password", + "website": "Website", + "datasheet": "Datasheet", + "package": "Package", + "importQuestion": "Import?", + "image": "Image", + "customerId": "Customer Id", + "orderAmount": "Order Amount", + "orderDate": "Order Date", + "trackingNumber": "Tracking Number", + "unspecified": "Unspecified", + "viewTracking": "View Tracking", + "preview": "Preview", + "serialNumberFormat": "Serial Number Format", + "family": "Family", + "source": "Source", + "qtyAvail": "QTY Avail.", + "keywords": "Keywords", + "apiEndpoint": "Api Endpoint", + "lastSerialNumber": "Last Serial Number", + "lowStock": "Low Stock", + "binNumber": "Bin Number", + "binNumber2": "Bin Number 2", + "manufacturerPart": "Manufacturer Part", + "packageType": "Package Type", + "primaryDatasheetUrl": "Primary Datasheet Url", + "productUrl": "Product Url", + "digikeyPartNumber": "DigiKey Part Number", + "mouserPartNumber": "Mouser Part Number", + "arrowPartNumber": "Arrow Part Number", + "minimumOrderQuantity": "Minimum Order Quantity", + "supplier": "Supplier", + "supplierPartNumber": "Supplier Part Number", + "quantityAvailable": "Quantity Available", + "rememberLastSelection": "Remember last selection", + "origin": "Origin", + "supplierType": "Supplier Type" + }, + "message": { + "noPartsAdded": "No parts added.", + "noChildPartTypes": "There are no child part types.", + "noResults": "No Results", + "loadingOrder": "Loading order# {{order}}", + "noMatchingResults":"No matching results.", + "notEnoughParts": "Not enough parts", + "noPartInfo": "No part information is available for '{{partNumber}}'. You are subscribed to updates and will be automatically updated when the part is indexed." + }, + "confirm": { + "deleteProject": "Are you sure you want to delete this project and your entire BOM?", + "removeBomParts": "Are you sure you want to remove these <1>{{quantity}} part(s) from your BOM?", + "deletePcb": "Are you sure you want to delete this PCB and it's parts from your BOM?", + "deletePartType": "Are you sure you want to delete part type <1>{{name}}?", + "deletePart": "Are you sure you want to delete part <1>{{name}}?", + "deleteLocalFile": "Are you sure you want to delete this local file named <1>{{name}}?", + "permanent": "This action is <1>permanent and cannot be recovered.", + "header": { + "removeBomPart": "Remove Part from BOM", + "deleteFile": "Delete File", + "deletePart": "Delete Part" + } + }, + "error": { + "deleteProjectFailed": "Failed to delete project!", + "failedToRemoveBomParts": "Failed to remove parts from BOM!", + "projectNotFound": "Could not find project named {{projectName}}", + "failedSaveProject": "Failed to save project change!", + "failedSavePcb": "Failed to save pcb!", + "failedDeleteProject": "Failed to remove project!", + "failedSavePartType": "Failed to save Part Type!", + "failedDeletePcb": "Failed to remove pcb!", + "noPartSelected": "No part selected!", + "failedAddPart": "Failed to add part!", + "failedAddPcb": "Failed to add pcb!", + "failedBomExport": "BOM export failed!", + "failedDeletedPartType": "Failed to delete part type {{name}}", + "failedAddedPartType": "Failed to add part type {{name}}", + "failedClearedCredentials": "Failed to clear cached credentials for {{apiName}}", + "testFailed": "Test failed", + "invalidOrder": "Hmmm, that doesn't look like a valid order number!", + "invalid2dBarcode": "Hmmm, I don't recognize that 2D barcode!" + }, + "success": { + "producedPcbs": "{{quantity}} PCB's were produced!", + "bomExported": "BOM exported successfully!", + "savedPartType": "Saved part type {{name}}", + "deletedPartType": "Deleted part type {{name}}", + "barcodeTypeReceived": "Barcode type {{type}} received", + "systemSettingsSaved": "System settings were saved.", + "clearedCredentials": "Successfully cleared cached credentials for {{apiName}}", + "testPassed": "Test passed", + "partsImported": "{{count}} parts were imported!", + "deletedProject": "Project was deleted!", + "deletedPcb": "Pcb was deleted!" + }, + "popup": { + "bom": { + "addPart": "Add a part to the BOM", + "downloadBom": "Download a BOM part list", + "removePart": "Remove selected parts from the BOM", + "addPcb": "Add a PCB to this project", + "producePcb": "Reduce inventory quantities when a PCB is assembled", + "filterOutOfStock": "Select to only show out of stock parts", + "pcb": "Indicates which PCB your part is assigned to. A PCB assignment is optional, all unassigned parts will appear in the <1>All tab.", + "quantity": "The quantity of parts required for a single PCB", + "inventoryQuantity": "The quantity of parts currently in inventory", + "referenceId": "Your custom <1 />reference Id(s) you can use for identifying this part.", + "schematicReferenceId": "Your custom <1 />schematic reference Id(s) that identify the part on the PCB silkscreen. <3/>Examples: <5>D1, <7>C2, <9>Q1", + "displayAll": "Displays all parts including parts not associated with a PCB.", + "unassociatedPartName": "Edit the name of your unassociated <1 /> part", + "bomQuantity": "Edit the <1 />BOM quantity required", + "bomCost": "Edit the part cost", + "quantityAvailable": "Edit the quantity available in your <1 />inventory", + "editReferenceId": "Edit your custom <1 />reference Id(s) you can use for identifying this part.", + "editSchematicReferenceId": "Edit your custom <1 />schematic reference Id(s) that identify the part on the PCB silkscreen. <3 />Examples: <5>D1, <7>C2, <9>Q1", + "customDescription": "Provide a custom description", + "customNote": "Provide a custom note" + } + }, + "footer": { + "version": "Version", + "promo": "Try <1>Binner Cloud Free" + }, + "notification": { + "versionBanner": { + "newVersion": "A new version of Binner <1>{{version}} is available!", + "releaseNotes": "Release Notes", + "view": "View", + "skip": "Skip", + "close": "Close" + } + }, + "color": { + "black": "Black", + "brown": "Brown", + "red": "Red", + "orange": "Orange", + "yellow": "Yellow", + "green": "Green", + "blue": "Blue", + "violet": "Violet", + "grey": "Grey", + "white": "White", + "gold": "Gold", + "silver": "Silver" + } } \ No newline at end of file diff --git a/Binner/Binner.Web/ClientApp/public/locales/fr/translation.json b/Binner/Binner.Web/ClientApp/public/locales/fr/translation.json index c1860fa3..a911851d 100644 --- a/Binner/Binner.Web/ClientApp/public/locales/fr/translation.json +++ b/Binner/Binner.Web/ClientApp/public/locales/fr/translation.json @@ -1,3 +1,596 @@ { - + "page": { + "home": { + "title": "Dashboard", + "description": "Binner is an inventory management app for makers, hobbyists and professionals.", + "addInventory": "Add Inventory", + "searchInventory": "Search Inventory", + "bom": "BOM", + "datasheets": "Datasheets", + "viewLowStock": "View Low Stock", + "partTypes": "Part Types", + "importExport": "Import/Export", + "printLabels": "Print Labels", + "tools": "Tools", + "settings": "Settings", + "yourOverview": "Your Overview", + "lowStock": "Low Stock", + "parts": "Parts", + "uniqueParts": "Unique Parts", + "value": "Value", + "projects": "Projects" + }, + "search": { + "title":"Inventory", + "search":"Seach" + }, + "inventory": { + "addtitle": "Add Inventory", + "edittitle": "Edit Inventory", + "datasheets": "Datasheets", + "pinout": "Pinout", + "circuits": "Circuits", + "productImages": "Product Images", + "referenceDesigns": "Reference Designs", + "bulkScan": "Bulk Scan", + "startScanning": "Start scanning parts...", + "recentlyAdded": "Recently Added", + "partMetadata": "Part Metadata", + "privatePartInfo": "Private Part Information", + "suppliers": "Suppliers", + "addKeyword": "Add Keyword", + "chooseAlternatePart": "Choose alternate part ({{count}})", + "placeholder": { + "location": "Home lab", + "keywords": "op amp", + "binNumber": "IC Components 2", + "binNumber2": "14" + }, + "popup": { + "bulkAddParts": "Bulk add parts using a barcode scanner", + "quantity": "Use the mousewheel and CTRL/ALT to change step size", + "lowStock": "Alert when the quantity gets below this value", + "location": "A custom value for identifying the parts location", + "binNumber": "A custom value for identifying the parts location", + "rememberLastSelection": "Enable this toggle to remember the last selected values of: <1>Part Type, Mounting Type, Quantity, Low Stock, Project, Location, Bin Number, Bin Number 2", + "clear": "Clear the form to default values", + "alternateParts": "Choose a different part to extract metadata information from. By default, Binner will give you the most relevant part and with the highest quantity available.", + "mustAddPart": "You must save the part before adding custom suppliers to it.", + "addSupplier": "Add a manual supplier entry", + "deleteLocalFile": "Delete this local file" + } + }, + "lowInventory": { + "title": "Low Inventory", + "description": "Use this page to reorder parts you are low on.<1/>You can define a custom <3>Low Stock value per part in your inventory." + }, + "bom": { + "bc": { + "home": "Home", + "boms": "BOMs", + "bom": "BOM" + }, + "header": { + "title": "Bill of Materials", + "description": "Manage your BOM by creating PCB(s) and adding your parts." + }, + "notEnoughPartsToProducePcb": "You do not have enough parts to produce this PCB.", + "notEnoughPartsToProduceBom": "You do not have enough parts to produce your entire BOM.", + "noPartsAssigned": "Assign parts to get a count of how many times you can produce this PCB.", + "pcbProduceCount": "You can produce this PCB {{count}} times with your current inventory.", + "bomProduceCount": "You can produce your entire BOM {{count}} times with your current inventory.", + "lowestPcb": "{{name}} is the lowest on inventory." + }, + "boms": { + "header": { + "description": "Bill of Materials, or BOM allows you to manage inventory quantities per project. You can reduce quantities for each PCB you produce, check which parts you need to buy more of and analyze costs.

Choose or create the project to manage BOM for.
" + } + }, + "partTypes": { + "title": "Part Types", + "description": "Part Types allow you to separate your parts by type. <1>Parent types allow for unlimited part type hierarchy.<3 />For example: OpAmps may be a sub-type of IC's, so OpAmp's parent type is IC." + }, + "exportData": { + "title": "Import/Export Data", + "description": "Import or Export your Binner database to a human-readable format.", + "uploadNote": "Drag a document to upload, or click to select files", + "acceptedFileTypes": "Accepted file types: \"*.sql, *.xls, *.xlsx, *.csv\"", + "importResult": "Import Result", + "totalRowsImported": "Total Rows Imported", + "totalProjectsImported": "Projects Imported", + "totalPartTypesImported": "Part Types Imported", + "totalPartsImported": "Parts Imported" + }, + "tools": { + "title": "Tools", + "description": "Binner includes a suite of free utilities common to daily life in electrical engineering.", + "resistorColorCodeCalc": "Resistor Color Code Calculator", + "ohmsLawCalc": "Ohms Law Calculator", + "voltageDividerCalc": "Voltage Divider Calculator", + "barcodeScanner": "Barcode Scanner" + }, + "barcodeScanner": { + "title": "Barcode Scanner", + "description": "Test your barcode scanner to see what values it outputs.", + "detected": "Detected", + "waitingForInput": "Waiting for input..." + }, + "settings": { + "title": "Settings", + "description": "Configure your integrations, printer configuration, as well as label part templates. <1 /> Additional help can be found on the <3>Wiki", + "confirm": { + "mustAuthenticateHeader": "Must Authenticate", + "mustAuthenticate": "External Api is requesting that you authenticate first. You will be redirected back after authenticating with the external provider." + }, + "integrations": "Integrations", + "integrationsDescription": "To integrate with DigiKey, Mouser, Arrow or Octopart/Nexar API's you must obtain API keys for each service you wish to use. <1 /> Adding integrations will greatly enhance your experience.", + "swarm": "Swarm", + "swarmDescription": "Swarm is a free API service provided by <1>Binner's cloud service that contains part metadata from many aggregate sources. It is the primary source of part, media and datasheet information. Registering for your own API Keys will give you higher request limits and can be obtained at <3>https://binner.io/swarm", + "swarmSupport": "Swarm Support", + "digikey": "DigiKey", + "digikeySupport": "DigiKey Support", + "mouser": "Mouser", + "mouserSupport": "Mouser Support", + "searchApiKey": "Search Api Key", + "ordersApiKey": "Orders Api Key", + "cartApiKey": "Cart Api Key", + "arrow": "Arrow", + "arrowSupport": "Arrow Support", + "octopartNexar": "Octopart/Nexar", + "octopartNexarSupport": "Octopart/Nexar Support", + "printerConfiguration": "Printer Configuration", + "printerConfigDescription": "Configure your printer name as it shows up in your environment (Windows Printers or CUPS Printer Name)", + "printerName": "Printer Name", + "partLabelSource": "Part Label Source", + "partLabelName": "Part Label Name", + "partLabelTemplate": "Part Label Template", + "partLabelTemplateDescription": "Part labels are printed according to this template.", + "lineX": "Line {{number}}", + "identifierX": "Identifier {{number}}" + }, + "datasheet": { + "title": "Datasheet Search" + }, + "orderImport": { + "title": "Order Import", + "enterOrderNumber": "Enter your order number for the supplier.", + "webOrderNum": "Web Order #", + "salesOrderNum": "Sales Order #", + "orderNum": "Order Number", + "instructions": "For <1>DigiKey orders, this is the <3>Sales Order #.<4 />For <6>Mouser orders, this is the <8>Web Order #.<10 />For <12>Arrow orders, this is the <14>Order Number.", + "mouserNote": "<0>Note: Mouser only supports Web Order # so make sure when importing that you are using the Web Order # and <2>not the Sales Order #", + "arrowNote": "<0>Note: Arrow requires that you first request access to their Order API by sending them an email. See <2>Arrow Order Api" + }, + "printLabels": { + "title": "Print Labels", + "description": "Print custom multi-line labels for your storage bins.<1 />Print history is kept so you can reuse templates for your labels.", + "printHistory": "Print History", + "label": { + "labelType": "Label Type", + "paperSource": "Paper Source", + "labelNum": "Label #", + "text": "Text", + "fontSize": "FontSize", + "alignment": "Alignment", + "topMargin": "Top Margin", + "leftMargin": "Left Margin", + "font": "Font", + "isBarcode": "Is Barcode", + "center": "Center" + } + }, + "project": { + "title": "Edit Project", + "description": "Projects are used as part of your BOM, allowing you to associate parts to multiple PCBs.", + "pcbs": "PCBs", + "confirm": { + "deleteProjectHeader": "Delete Project", + "deletePcbHeader": "Delete Pcb" + }, + "placeholder": { + "name": "My Project", + "location": "New York" + }, + "popup": { + "name": "Enter the name of your project/BOM", + "description": "Enter a description that summarizes your project", + "location": "Your project's location <1>(optional)", + "color": "Associate a color with this project for easy identification" + } + }, + "tool": { + "ohmsLaw": { + "title": "Ohms Law Calculator", + "description": "Ohms Law explains the relationship between voltage, current and resistance. Input any 2 values to calculate the other 2 values.", + "voltage": "Voltage", + "current": "Current", + "resistance": "Resistance", + "power": "Power" + }, + "resistanceColorCalc": { + "title": "Resistor Color Code Calculator", + "4band": "4 Band", + "5band": "5 Band", + "6band": "6 Band", + "numOfBands": "Choose the number of bands", + "chooseBands": "Choose bands...", + "resistance": "Resistance" + }, + "voltDividerCalc": { + "title": "Voltage Divider Calculator", + "description": "A voltage divider uses 2 resistors to reduce a voltage to a fraction of its input voltage.", + "voltageInput": "Voltage Input", + "resistor": "Resistor", + "outputVoltage": "Output Voltage" + } + } + }, + "bc": { + "home": "Home", + "boms": "BOMs", + "bom": "BOM", + "editProject": "Edit Project", + "tools": "Tools", + "ohmsLaw": "Ohms Law Calculator", + "resistanceColorCalc": "Resistor Color Code Calculator", + "voltDividerCalc": "Voltage Divider Calculator" + }, + "comp": { + "navBar": { + "search": "Search", + "help": "Help", + "home": "Home", + "addInventory": "Add Inventory", + "orderImport": "Order Import" + }, + "partsGrid": { + "recordsPerPage": "records per page", + "parts": "Parts", + "part": "Part", + "quantity": "Quantity", + "lowStock": "Low Stock", + "mfrPart": "Manufacturer Part", + "description": "Description", + "location": "Location", + "binNumber": "Bin Number", + "binNumber2": "Bin Number 2", + "cost": "Cost", + "digikeyPart": "DigiKey Part", + "mouserPart": "Mouser Part", + "arrowPart": "Arrow Part", + "datasheet": "Datasheet", + "noResults": "No results.", + "ok": "Ok", + "popup": { + "lowStock": "Quantities below this value will indicate the part is low on stock.", + "quantity": "The quantity of parts currently in stock." + }, + "confirm": { + "deletePart": "Are you sure you want to delete part {{partNumber}}?" + }, + "error": { + "failedSave": "Error saving part {{partNumber}} - {{statusText}}" + } + }, + "addBomPartModal": { + "title": "BOM Management", + "description": "Add a part to your BOM, optionally associating it with a particular PCB.", + "none": "None", + "confirmHeader": "Add Part", + "confirmAddUnassociated": "You have not selected a part from your inventory.<1/>Are you sure you want to add this part without associating it to a part in your inventory?<3/><4/><5>Note: You will still be able to manage it's quantity if you choose to proceed, but it will not appear in your inventory.", + "selectPcb": "Select PCB", + "choosePcb": "Choose PCB", + "popup": { + "selectPcb": "Select the pcb you would like to add parts to. If you choose not to select a PCB, the part will be added to your BOM without PCB associations.", + "quantity": "Enter the quantity of this part required to produce a single PCB.", + "referenceIds": "Enter a custom Reference Id you can use for identifying this part.<1/>Examples: <3>Optoisolator1, <5>Capacitor Array", + "schematicReferenceIds": "Enter one or more Schematic Reference Ids you can use for identifying this part on the PCB silkscreen.<1/>Examples: <3>D1, <5>C2, <7>Q1", + "notes": "Enter any custom notes for this part.", + "partNumber": "Search for a part in your inventory", + "customDescription": "Enter your own custom description for this part." + } + }, + "addPcbModal": { + "title": "BOM Management", + "description": "Adding a PCB allows you to associate your parts with a specific PCB, and even multiple PCBs within a project.", + "header": "Add PCB", + "popup": { + "name": "Enter the name of your pcb board or module", + "description": "Enter a description describing what your pcb does. (<1>optional)", + "quantity": "Enter a quantity (multiplier) of PCB's produced each time you create a PCB. This should normally be 1, unless you require several copies of the same PCB for producing your BOM project.<1/><2/><3>Example: An audio amplifier may require 2 of the same PCB's, one for each left/right channel each time you produce the entire assembly.", + "cost": "The cost to produce a single PCB board (without components). If using quantity, only specify the cost for a single board as quantity will be taken into consideration.", + "serialNumberFormat": "Enter your serial number format as a string. The left-most portion of the string will be incremented by 1 each time you produce a PCB. (<1>optional)<2/>Example: <4>SN00000000" + }, + "placeholder": { + "name": "Main Board or module name", + "description": "Description of pcb" + } + }, + "producePcbModal": { + "title": "BOM Management", + "description": "", + "header": "Produce Pcb", + "nextSerialNumber": "Next Serial Number", + "maxQty": "Max Qty", + "options": { + "all": "All", + "allDescription": "Produce the entire BOM", + "unassociated": "Unassociated", + "unassociatedDescription": "Produce parts not associated to a PCB" + }, + "popup": { + "pcbs": "Select the pcb(s) you would like to produce. If you don't define PCB's, choose Unassociated or All.", + "quantity": "Enter the quantity of PCBs you are producing.", + "nextSerialNumber": "The next serial number assigned to the board", + "maxQty": "The maximum number of boards you can produce", + "parts": "The number of parts on the board", + "outOfStock": "The number of parts on the board that are out of stock", + "serialNumber": "The next PCB will have it's serial number started at this value." + }, + "label": { + "pcbs": "Select PCB(s)" + }, + "placeholder": { + "pcbs": "Choose PCB(s) to produce" + } + }, + "chooseAlternatePartModal": { + "title": "Matching Parts" + }, + "lineTemplate": { + "label": { + "content": "Content", + "font": "Font", + "fontSize": "Font Size", + "fontColor": "Font Color", + "textPosition": "Text Position", + "upperCaseText": "UpperCase Text", + "lowerCaseText": "LowerCase Text", + "barcode": "Barcode", + "marginLeft": "Margin Left", + "marginTop": "Margin Top", + "rotateDegrees": "Rotate Degrees" + }, + "popup": { + "content": "Template can reference any part field, example: {partNumber}, {description}, {manufacturer}, {location}, {binNumber}, {cost} etc. See <1>Wiki for all available tags.", + "autoSize": "Text size will be automatically determined.", + "upperCaseText": "Render the text as all upper-case characters.", + "lowerCaseText": "Render the text as all lower-case characters.", + "barcode": "Render the Content value encoded as a barcode.", + "rotateDegrees": "Rotate the text in degrees. Example: 90" + } + } + }, + "button": { + "addBomProject": "Add BOM Project", + "addPart": "Add Part", + "save": "Save", + "download": "Download", + "addPcb": "Add PCB", + "producePcb": "Produce PCB", + "removePart": "Remove Part", + "removeXParts": "Remove ({{checkboxesChecked}}) Parts", + "addFirstPart": "Add your first part!", + "all": "All", + "addPartType": "Add Part Type", + "manualAdd": "Manual Add", + "back": "Back", + "import": "Import", + "export": "Export", + "testApi": "Test Api", + "forgetCredentials": "Forget Credentials", + "viewDatasheet": "View Datasheet", + "search": "Search", + "clear": "Clear", + "reset": "Reset", + "importParts": "Import Parts", + "preview": "Preview", + "print": "Print", + "load": "Load", + "addLine": "Add Line", + "add": "Add", + "cancel": "Cancel", + "produce": "Produce", + "delete": "Delete", + "visit": "Visit" + }, + "label": { + "name": "Name", + "description": "Description", + "location": "Location", + "color": "Color", + "project": "Project", + "parts": "Parts", + "pcb": "Pcb", + "pcbs": "Pcbs", + "delete": "Delete", + "deleteProject": "Delete Project", + "editProject": "Edit Project", + "lastModified": "Last modified", + "inStock": "In Stock", + "outOfStock": "Out of Stock", + "totalParts": "Total Parts", + "producible": "Producible", + "part": "Part", + "partNumber": "Part Number", + "partNumberShort": "Part#", + "supplierPart": "Supplier Part", + "mfrPart": "Mfr Part", + "manufacturer": "Manufacturer", + "partType": "Part Type", + "mountingType": "Mounting Type", + "cost": "Cost", + "quantity": "Quantity", + "quantityShort": "Qty", + "leadTime": "Lead Time", + "referenceIds": "Reference Id(s)", + "schematicReferenceIds": "Schematic Reference Id(s)", + "customDescription": "Custom Description", + "note": "Note", + "notes": "Notes", + "recordsPerPage": "records per page", + "parent": "Parent", + "partsCount": "Parts Count", + "systemType": "System Type", + "resistors": "Resistors", + "hideEmptyTypes": "Hide Empty Types", + "error": "Error", + "errors": "Errors", + "or": "Or", + "status": "Status", + "success": "Success", + "failed": "Failed", + "warnings": "Warnings", + "apiKey": "Api Key", + "apiUrl": "Api Url", + "clientId": "Client Id", + "clientSecret": "Client Secret", + "timeout": "Timeout", + "postbackUrl": "Postback Url", + "username": "Username", + "password": "Password", + "website": "Website", + "datasheet": "Datasheet", + "package": "Package", + "importQuestion": "Import?", + "image": "Image", + "customerId": "Customer Id", + "orderAmount": "Order Amount", + "orderDate": "Order Date", + "trackingNumber": "Tracking Number", + "unspecified": "Unspecified", + "viewTracking": "View Tracking", + "preview": "Preview", + "serialNumberFormat": "Serial Number Format", + "family": "Family", + "source": "Source", + "qtyAvail": "QTY Avail.", + "keywords": "Keywords", + "apiEndpoint": "Api Endpoint", + "lastSerialNumber": "Last Serial Number", + "lowStock": "Low Stock", + "binNumber": "Bin Number", + "binNumber2": "Bin Number 2", + "manufacturerPart": "Manufacturer Part", + "packageType": "Package Type", + "primaryDatasheetUrl": "Primary Datasheet Url", + "productUrl": "Product Url", + "digikeyPartNumber": "DigiKey Part Number", + "mouserPartNumber": "Mouser Part Number", + "arrowPartNumber": "Arrow Part Number", + "minimumOrderQuantity": "Minimum Order Quantity", + "supplier": "Supplier", + "supplierPartNumber": "Supplier Part Number", + "quantityAvailable": "Quantity Available", + "rememberLastSelection": "Remember last selection", + "origin": "Origin", + "supplierType": "Supplier Type" + }, + "message": { + "noPartsAdded": "No parts added.", + "noChildPartTypes": "There are no child part types.", + "noResults": "No Results", + "loadingOrder": "Loading order# {{order}}", + "noMatchingResults":"No matching results.", + "notEnoughParts": "Not enough parts", + "noPartInfo": "No part information is available for '{{partNumber}}'. You are subscribed to updates and will be automatically updated when the part is indexed." + }, + "confirm": { + "deleteProject": "Are you sure you want to delete this project and your entire BOM?", + "removeBomParts": "Are you sure you want to remove these <1>{{quantity}} part(s) from your BOM?", + "deletePcb": "Are you sure you want to delete this PCB and it's parts from your BOM?", + "deletePartType": "Are you sure you want to delete part type <1>{{name}}?", + "deletePart": "Are you sure you want to delete part <1>{{name}}?", + "deleteLocalFile": "Are you sure you want to delete this local file named <1>{{name}}?", + "permanent": "This action is <1>permanent and cannot be recovered.", + "header": { + "removeBomPart": "Remove Part from BOM", + "deleteFile": "Delete File", + "deletePart": "Delete Part" + } + }, + "error": { + "deleteProjectFailed": "Failed to delete project!", + "failedToRemoveBomParts": "Failed to remove parts from BOM!", + "projectNotFound": "Could not find project named {{projectName}}", + "failedSaveProject": "Failed to save project change!", + "failedSavePcb": "Failed to save pcb!", + "failedDeleteProject": "Failed to remove project!", + "failedSavePartType": "Failed to save Part Type!", + "failedDeletePcb": "Failed to remove pcb!", + "noPartSelected": "No part selected!", + "failedAddPart": "Failed to add part!", + "failedAddPcb": "Failed to add pcb!", + "failedBomExport": "BOM export failed!", + "failedDeletedPartType": "Failed to delete part type {{name}}", + "failedAddedPartType": "Failed to add part type {{name}}", + "failedClearedCredentials": "Failed to clear cached credentials for {{apiName}}", + "testFailed": "Test failed", + "invalidOrder": "Hmmm, that doesn't look like a valid order number!", + "invalid2dBarcode": "Hmmm, I don't recognize that 2D barcode!" + }, + "success": { + "producedPcbs": "{{quantity}} PCB's were produced!", + "bomExported": "BOM exported successfully!", + "savedPartType": "Saved part type {{name}}", + "deletedPartType": "Deleted part type {{name}}", + "barcodeTypeReceived": "Barcode type {{type}} received", + "systemSettingsSaved": "System settings were saved.", + "clearedCredentials": "Successfully cleared cached credentials for {{apiName}}", + "testPassed": "Test passed", + "partsImported": "{{count}} parts were imported!", + "deletedProject": "Project was deleted!", + "deletedPcb": "Pcb was deleted!" + }, + "popup": { + "bom": { + "addPart": "Add a part to the BOM", + "downloadBom": "Download a BOM part list", + "removePart": "Remove selected parts from the BOM", + "addPcb": "Add a PCB to this project", + "producePcb": "Reduce inventory quantities when a PCB is assembled", + "filterOutOfStock": "Select to only show out of stock parts", + "pcb": "Indicates which PCB your part is assigned to. A PCB assignment is optional, all unassigned parts will appear in the <1>All tab.", + "quantity": "The quantity of parts required for a single PCB", + "inventoryQuantity": "The quantity of parts currently in inventory", + "referenceId": "Your custom <1 />reference Id(s) you can use for identifying this part.", + "schematicReferenceId": "Your custom <1 />schematic reference Id(s) that identify the part on the PCB silkscreen. <3/>Examples: <5>D1, <7>C2, <9>Q1", + "displayAll": "Displays all parts including parts not associated with a PCB.", + "unassociatedPartName": "Edit the name of your unassociated <1 /> part", + "bomQuantity": "Edit the <1 />BOM quantity required", + "bomCost": "Edit the part cost", + "quantityAvailable": "Edit the quantity available in your <1 />inventory", + "editReferenceId": "Edit your custom <1 />reference Id(s) you can use for identifying this part.", + "editSchematicReferenceId": "Edit your custom <1 />schematic reference Id(s) that identify the part on the PCB silkscreen. <3 />Examples: <5>D1, <7>C2, <9>Q1", + "customDescription": "Provide a custom description", + "customNote": "Provide a custom note" + } + }, + "footer": { + "version": "Version", + "promo": "Try <1>Binner Cloud Free" + }, + "notification": { + "versionBanner": { + "newVersion": "A new version of Binner <1>{{version}} is available!", + "releaseNotes": "Release Notes", + "view": "View", + "skip": "Skip", + "close": "Close" + } + }, + "color": { + "black": "Black", + "brown": "Brown", + "red": "Red", + "orange": "Orange", + "yellow": "Yellow", + "green": "Green", + "blue": "Blue", + "violet": "Violet", + "grey": "Grey", + "white": "White", + "gold": "Gold", + "silver": "Silver" + } } \ No newline at end of file diff --git a/Binner/Binner.Web/ClientApp/public/locales/zh/translation.json b/Binner/Binner.Web/ClientApp/public/locales/zh/translation.json index c1860fa3..a911851d 100644 --- a/Binner/Binner.Web/ClientApp/public/locales/zh/translation.json +++ b/Binner/Binner.Web/ClientApp/public/locales/zh/translation.json @@ -1,3 +1,596 @@ { - + "page": { + "home": { + "title": "Dashboard", + "description": "Binner is an inventory management app for makers, hobbyists and professionals.", + "addInventory": "Add Inventory", + "searchInventory": "Search Inventory", + "bom": "BOM", + "datasheets": "Datasheets", + "viewLowStock": "View Low Stock", + "partTypes": "Part Types", + "importExport": "Import/Export", + "printLabels": "Print Labels", + "tools": "Tools", + "settings": "Settings", + "yourOverview": "Your Overview", + "lowStock": "Low Stock", + "parts": "Parts", + "uniqueParts": "Unique Parts", + "value": "Value", + "projects": "Projects" + }, + "search": { + "title":"Inventory", + "search":"Seach" + }, + "inventory": { + "addtitle": "Add Inventory", + "edittitle": "Edit Inventory", + "datasheets": "Datasheets", + "pinout": "Pinout", + "circuits": "Circuits", + "productImages": "Product Images", + "referenceDesigns": "Reference Designs", + "bulkScan": "Bulk Scan", + "startScanning": "Start scanning parts...", + "recentlyAdded": "Recently Added", + "partMetadata": "Part Metadata", + "privatePartInfo": "Private Part Information", + "suppliers": "Suppliers", + "addKeyword": "Add Keyword", + "chooseAlternatePart": "Choose alternate part ({{count}})", + "placeholder": { + "location": "Home lab", + "keywords": "op amp", + "binNumber": "IC Components 2", + "binNumber2": "14" + }, + "popup": { + "bulkAddParts": "Bulk add parts using a barcode scanner", + "quantity": "Use the mousewheel and CTRL/ALT to change step size", + "lowStock": "Alert when the quantity gets below this value", + "location": "A custom value for identifying the parts location", + "binNumber": "A custom value for identifying the parts location", + "rememberLastSelection": "Enable this toggle to remember the last selected values of: <1>Part Type, Mounting Type, Quantity, Low Stock, Project, Location, Bin Number, Bin Number 2", + "clear": "Clear the form to default values", + "alternateParts": "Choose a different part to extract metadata information from. By default, Binner will give you the most relevant part and with the highest quantity available.", + "mustAddPart": "You must save the part before adding custom suppliers to it.", + "addSupplier": "Add a manual supplier entry", + "deleteLocalFile": "Delete this local file" + } + }, + "lowInventory": { + "title": "Low Inventory", + "description": "Use this page to reorder parts you are low on.<1/>You can define a custom <3>Low Stock value per part in your inventory." + }, + "bom": { + "bc": { + "home": "Home", + "boms": "BOMs", + "bom": "BOM" + }, + "header": { + "title": "Bill of Materials", + "description": "Manage your BOM by creating PCB(s) and adding your parts." + }, + "notEnoughPartsToProducePcb": "You do not have enough parts to produce this PCB.", + "notEnoughPartsToProduceBom": "You do not have enough parts to produce your entire BOM.", + "noPartsAssigned": "Assign parts to get a count of how many times you can produce this PCB.", + "pcbProduceCount": "You can produce this PCB {{count}} times with your current inventory.", + "bomProduceCount": "You can produce your entire BOM {{count}} times with your current inventory.", + "lowestPcb": "{{name}} is the lowest on inventory." + }, + "boms": { + "header": { + "description": "Bill of Materials, or BOM allows you to manage inventory quantities per project. You can reduce quantities for each PCB you produce, check which parts you need to buy more of and analyze costs.

Choose or create the project to manage BOM for.
" + } + }, + "partTypes": { + "title": "Part Types", + "description": "Part Types allow you to separate your parts by type. <1>Parent types allow for unlimited part type hierarchy.<3 />For example: OpAmps may be a sub-type of IC's, so OpAmp's parent type is IC." + }, + "exportData": { + "title": "Import/Export Data", + "description": "Import or Export your Binner database to a human-readable format.", + "uploadNote": "Drag a document to upload, or click to select files", + "acceptedFileTypes": "Accepted file types: \"*.sql, *.xls, *.xlsx, *.csv\"", + "importResult": "Import Result", + "totalRowsImported": "Total Rows Imported", + "totalProjectsImported": "Projects Imported", + "totalPartTypesImported": "Part Types Imported", + "totalPartsImported": "Parts Imported" + }, + "tools": { + "title": "Tools", + "description": "Binner includes a suite of free utilities common to daily life in electrical engineering.", + "resistorColorCodeCalc": "Resistor Color Code Calculator", + "ohmsLawCalc": "Ohms Law Calculator", + "voltageDividerCalc": "Voltage Divider Calculator", + "barcodeScanner": "Barcode Scanner" + }, + "barcodeScanner": { + "title": "Barcode Scanner", + "description": "Test your barcode scanner to see what values it outputs.", + "detected": "Detected", + "waitingForInput": "Waiting for input..." + }, + "settings": { + "title": "Settings", + "description": "Configure your integrations, printer configuration, as well as label part templates. <1 /> Additional help can be found on the <3>Wiki", + "confirm": { + "mustAuthenticateHeader": "Must Authenticate", + "mustAuthenticate": "External Api is requesting that you authenticate first. You will be redirected back after authenticating with the external provider." + }, + "integrations": "Integrations", + "integrationsDescription": "To integrate with DigiKey, Mouser, Arrow or Octopart/Nexar API's you must obtain API keys for each service you wish to use. <1 /> Adding integrations will greatly enhance your experience.", + "swarm": "Swarm", + "swarmDescription": "Swarm is a free API service provided by <1>Binner's cloud service that contains part metadata from many aggregate sources. It is the primary source of part, media and datasheet information. Registering for your own API Keys will give you higher request limits and can be obtained at <3>https://binner.io/swarm", + "swarmSupport": "Swarm Support", + "digikey": "DigiKey", + "digikeySupport": "DigiKey Support", + "mouser": "Mouser", + "mouserSupport": "Mouser Support", + "searchApiKey": "Search Api Key", + "ordersApiKey": "Orders Api Key", + "cartApiKey": "Cart Api Key", + "arrow": "Arrow", + "arrowSupport": "Arrow Support", + "octopartNexar": "Octopart/Nexar", + "octopartNexarSupport": "Octopart/Nexar Support", + "printerConfiguration": "Printer Configuration", + "printerConfigDescription": "Configure your printer name as it shows up in your environment (Windows Printers or CUPS Printer Name)", + "printerName": "Printer Name", + "partLabelSource": "Part Label Source", + "partLabelName": "Part Label Name", + "partLabelTemplate": "Part Label Template", + "partLabelTemplateDescription": "Part labels are printed according to this template.", + "lineX": "Line {{number}}", + "identifierX": "Identifier {{number}}" + }, + "datasheet": { + "title": "Datasheet Search" + }, + "orderImport": { + "title": "Order Import", + "enterOrderNumber": "Enter your order number for the supplier.", + "webOrderNum": "Web Order #", + "salesOrderNum": "Sales Order #", + "orderNum": "Order Number", + "instructions": "For <1>DigiKey orders, this is the <3>Sales Order #.<4 />For <6>Mouser orders, this is the <8>Web Order #.<10 />For <12>Arrow orders, this is the <14>Order Number.", + "mouserNote": "<0>Note: Mouser only supports Web Order # so make sure when importing that you are using the Web Order # and <2>not the Sales Order #", + "arrowNote": "<0>Note: Arrow requires that you first request access to their Order API by sending them an email. See <2>Arrow Order Api" + }, + "printLabels": { + "title": "Print Labels", + "description": "Print custom multi-line labels for your storage bins.<1 />Print history is kept so you can reuse templates for your labels.", + "printHistory": "Print History", + "label": { + "labelType": "Label Type", + "paperSource": "Paper Source", + "labelNum": "Label #", + "text": "Text", + "fontSize": "FontSize", + "alignment": "Alignment", + "topMargin": "Top Margin", + "leftMargin": "Left Margin", + "font": "Font", + "isBarcode": "Is Barcode", + "center": "Center" + } + }, + "project": { + "title": "Edit Project", + "description": "Projects are used as part of your BOM, allowing you to associate parts to multiple PCBs.", + "pcbs": "PCBs", + "confirm": { + "deleteProjectHeader": "Delete Project", + "deletePcbHeader": "Delete Pcb" + }, + "placeholder": { + "name": "My Project", + "location": "New York" + }, + "popup": { + "name": "Enter the name of your project/BOM", + "description": "Enter a description that summarizes your project", + "location": "Your project's location <1>(optional)", + "color": "Associate a color with this project for easy identification" + } + }, + "tool": { + "ohmsLaw": { + "title": "Ohms Law Calculator", + "description": "Ohms Law explains the relationship between voltage, current and resistance. Input any 2 values to calculate the other 2 values.", + "voltage": "Voltage", + "current": "Current", + "resistance": "Resistance", + "power": "Power" + }, + "resistanceColorCalc": { + "title": "Resistor Color Code Calculator", + "4band": "4 Band", + "5band": "5 Band", + "6band": "6 Band", + "numOfBands": "Choose the number of bands", + "chooseBands": "Choose bands...", + "resistance": "Resistance" + }, + "voltDividerCalc": { + "title": "Voltage Divider Calculator", + "description": "A voltage divider uses 2 resistors to reduce a voltage to a fraction of its input voltage.", + "voltageInput": "Voltage Input", + "resistor": "Resistor", + "outputVoltage": "Output Voltage" + } + } + }, + "bc": { + "home": "Home", + "boms": "BOMs", + "bom": "BOM", + "editProject": "Edit Project", + "tools": "Tools", + "ohmsLaw": "Ohms Law Calculator", + "resistanceColorCalc": "Resistor Color Code Calculator", + "voltDividerCalc": "Voltage Divider Calculator" + }, + "comp": { + "navBar": { + "search": "Search", + "help": "Help", + "home": "Home", + "addInventory": "Add Inventory", + "orderImport": "Order Import" + }, + "partsGrid": { + "recordsPerPage": "records per page", + "parts": "Parts", + "part": "Part", + "quantity": "Quantity", + "lowStock": "Low Stock", + "mfrPart": "Manufacturer Part", + "description": "Description", + "location": "Location", + "binNumber": "Bin Number", + "binNumber2": "Bin Number 2", + "cost": "Cost", + "digikeyPart": "DigiKey Part", + "mouserPart": "Mouser Part", + "arrowPart": "Arrow Part", + "datasheet": "Datasheet", + "noResults": "No results.", + "ok": "Ok", + "popup": { + "lowStock": "Quantities below this value will indicate the part is low on stock.", + "quantity": "The quantity of parts currently in stock." + }, + "confirm": { + "deletePart": "Are you sure you want to delete part {{partNumber}}?" + }, + "error": { + "failedSave": "Error saving part {{partNumber}} - {{statusText}}" + } + }, + "addBomPartModal": { + "title": "BOM Management", + "description": "Add a part to your BOM, optionally associating it with a particular PCB.", + "none": "None", + "confirmHeader": "Add Part", + "confirmAddUnassociated": "You have not selected a part from your inventory.<1/>Are you sure you want to add this part without associating it to a part in your inventory?<3/><4/><5>Note: You will still be able to manage it's quantity if you choose to proceed, but it will not appear in your inventory.", + "selectPcb": "Select PCB", + "choosePcb": "Choose PCB", + "popup": { + "selectPcb": "Select the pcb you would like to add parts to. If you choose not to select a PCB, the part will be added to your BOM without PCB associations.", + "quantity": "Enter the quantity of this part required to produce a single PCB.", + "referenceIds": "Enter a custom Reference Id you can use for identifying this part.<1/>Examples: <3>Optoisolator1, <5>Capacitor Array", + "schematicReferenceIds": "Enter one or more Schematic Reference Ids you can use for identifying this part on the PCB silkscreen.<1/>Examples: <3>D1, <5>C2, <7>Q1", + "notes": "Enter any custom notes for this part.", + "partNumber": "Search for a part in your inventory", + "customDescription": "Enter your own custom description for this part." + } + }, + "addPcbModal": { + "title": "BOM Management", + "description": "Adding a PCB allows you to associate your parts with a specific PCB, and even multiple PCBs within a project.", + "header": "Add PCB", + "popup": { + "name": "Enter the name of your pcb board or module", + "description": "Enter a description describing what your pcb does. (<1>optional)", + "quantity": "Enter a quantity (multiplier) of PCB's produced each time you create a PCB. This should normally be 1, unless you require several copies of the same PCB for producing your BOM project.<1/><2/><3>Example: An audio amplifier may require 2 of the same PCB's, one for each left/right channel each time you produce the entire assembly.", + "cost": "The cost to produce a single PCB board (without components). If using quantity, only specify the cost for a single board as quantity will be taken into consideration.", + "serialNumberFormat": "Enter your serial number format as a string. The left-most portion of the string will be incremented by 1 each time you produce a PCB. (<1>optional)<2/>Example: <4>SN00000000" + }, + "placeholder": { + "name": "Main Board or module name", + "description": "Description of pcb" + } + }, + "producePcbModal": { + "title": "BOM Management", + "description": "", + "header": "Produce Pcb", + "nextSerialNumber": "Next Serial Number", + "maxQty": "Max Qty", + "options": { + "all": "All", + "allDescription": "Produce the entire BOM", + "unassociated": "Unassociated", + "unassociatedDescription": "Produce parts not associated to a PCB" + }, + "popup": { + "pcbs": "Select the pcb(s) you would like to produce. If you don't define PCB's, choose Unassociated or All.", + "quantity": "Enter the quantity of PCBs you are producing.", + "nextSerialNumber": "The next serial number assigned to the board", + "maxQty": "The maximum number of boards you can produce", + "parts": "The number of parts on the board", + "outOfStock": "The number of parts on the board that are out of stock", + "serialNumber": "The next PCB will have it's serial number started at this value." + }, + "label": { + "pcbs": "Select PCB(s)" + }, + "placeholder": { + "pcbs": "Choose PCB(s) to produce" + } + }, + "chooseAlternatePartModal": { + "title": "Matching Parts" + }, + "lineTemplate": { + "label": { + "content": "Content", + "font": "Font", + "fontSize": "Font Size", + "fontColor": "Font Color", + "textPosition": "Text Position", + "upperCaseText": "UpperCase Text", + "lowerCaseText": "LowerCase Text", + "barcode": "Barcode", + "marginLeft": "Margin Left", + "marginTop": "Margin Top", + "rotateDegrees": "Rotate Degrees" + }, + "popup": { + "content": "Template can reference any part field, example: {partNumber}, {description}, {manufacturer}, {location}, {binNumber}, {cost} etc. See <1>Wiki for all available tags.", + "autoSize": "Text size will be automatically determined.", + "upperCaseText": "Render the text as all upper-case characters.", + "lowerCaseText": "Render the text as all lower-case characters.", + "barcode": "Render the Content value encoded as a barcode.", + "rotateDegrees": "Rotate the text in degrees. Example: 90" + } + } + }, + "button": { + "addBomProject": "Add BOM Project", + "addPart": "Add Part", + "save": "Save", + "download": "Download", + "addPcb": "Add PCB", + "producePcb": "Produce PCB", + "removePart": "Remove Part", + "removeXParts": "Remove ({{checkboxesChecked}}) Parts", + "addFirstPart": "Add your first part!", + "all": "All", + "addPartType": "Add Part Type", + "manualAdd": "Manual Add", + "back": "Back", + "import": "Import", + "export": "Export", + "testApi": "Test Api", + "forgetCredentials": "Forget Credentials", + "viewDatasheet": "View Datasheet", + "search": "Search", + "clear": "Clear", + "reset": "Reset", + "importParts": "Import Parts", + "preview": "Preview", + "print": "Print", + "load": "Load", + "addLine": "Add Line", + "add": "Add", + "cancel": "Cancel", + "produce": "Produce", + "delete": "Delete", + "visit": "Visit" + }, + "label": { + "name": "Name", + "description": "Description", + "location": "Location", + "color": "Color", + "project": "Project", + "parts": "Parts", + "pcb": "Pcb", + "pcbs": "Pcbs", + "delete": "Delete", + "deleteProject": "Delete Project", + "editProject": "Edit Project", + "lastModified": "Last modified", + "inStock": "In Stock", + "outOfStock": "Out of Stock", + "totalParts": "Total Parts", + "producible": "Producible", + "part": "Part", + "partNumber": "Part Number", + "partNumberShort": "Part#", + "supplierPart": "Supplier Part", + "mfrPart": "Mfr Part", + "manufacturer": "Manufacturer", + "partType": "Part Type", + "mountingType": "Mounting Type", + "cost": "Cost", + "quantity": "Quantity", + "quantityShort": "Qty", + "leadTime": "Lead Time", + "referenceIds": "Reference Id(s)", + "schematicReferenceIds": "Schematic Reference Id(s)", + "customDescription": "Custom Description", + "note": "Note", + "notes": "Notes", + "recordsPerPage": "records per page", + "parent": "Parent", + "partsCount": "Parts Count", + "systemType": "System Type", + "resistors": "Resistors", + "hideEmptyTypes": "Hide Empty Types", + "error": "Error", + "errors": "Errors", + "or": "Or", + "status": "Status", + "success": "Success", + "failed": "Failed", + "warnings": "Warnings", + "apiKey": "Api Key", + "apiUrl": "Api Url", + "clientId": "Client Id", + "clientSecret": "Client Secret", + "timeout": "Timeout", + "postbackUrl": "Postback Url", + "username": "Username", + "password": "Password", + "website": "Website", + "datasheet": "Datasheet", + "package": "Package", + "importQuestion": "Import?", + "image": "Image", + "customerId": "Customer Id", + "orderAmount": "Order Amount", + "orderDate": "Order Date", + "trackingNumber": "Tracking Number", + "unspecified": "Unspecified", + "viewTracking": "View Tracking", + "preview": "Preview", + "serialNumberFormat": "Serial Number Format", + "family": "Family", + "source": "Source", + "qtyAvail": "QTY Avail.", + "keywords": "Keywords", + "apiEndpoint": "Api Endpoint", + "lastSerialNumber": "Last Serial Number", + "lowStock": "Low Stock", + "binNumber": "Bin Number", + "binNumber2": "Bin Number 2", + "manufacturerPart": "Manufacturer Part", + "packageType": "Package Type", + "primaryDatasheetUrl": "Primary Datasheet Url", + "productUrl": "Product Url", + "digikeyPartNumber": "DigiKey Part Number", + "mouserPartNumber": "Mouser Part Number", + "arrowPartNumber": "Arrow Part Number", + "minimumOrderQuantity": "Minimum Order Quantity", + "supplier": "Supplier", + "supplierPartNumber": "Supplier Part Number", + "quantityAvailable": "Quantity Available", + "rememberLastSelection": "Remember last selection", + "origin": "Origin", + "supplierType": "Supplier Type" + }, + "message": { + "noPartsAdded": "No parts added.", + "noChildPartTypes": "There are no child part types.", + "noResults": "No Results", + "loadingOrder": "Loading order# {{order}}", + "noMatchingResults":"No matching results.", + "notEnoughParts": "Not enough parts", + "noPartInfo": "No part information is available for '{{partNumber}}'. You are subscribed to updates and will be automatically updated when the part is indexed." + }, + "confirm": { + "deleteProject": "Are you sure you want to delete this project and your entire BOM?", + "removeBomParts": "Are you sure you want to remove these <1>{{quantity}} part(s) from your BOM?", + "deletePcb": "Are you sure you want to delete this PCB and it's parts from your BOM?", + "deletePartType": "Are you sure you want to delete part type <1>{{name}}?", + "deletePart": "Are you sure you want to delete part <1>{{name}}?", + "deleteLocalFile": "Are you sure you want to delete this local file named <1>{{name}}?", + "permanent": "This action is <1>permanent and cannot be recovered.", + "header": { + "removeBomPart": "Remove Part from BOM", + "deleteFile": "Delete File", + "deletePart": "Delete Part" + } + }, + "error": { + "deleteProjectFailed": "Failed to delete project!", + "failedToRemoveBomParts": "Failed to remove parts from BOM!", + "projectNotFound": "Could not find project named {{projectName}}", + "failedSaveProject": "Failed to save project change!", + "failedSavePcb": "Failed to save pcb!", + "failedDeleteProject": "Failed to remove project!", + "failedSavePartType": "Failed to save Part Type!", + "failedDeletePcb": "Failed to remove pcb!", + "noPartSelected": "No part selected!", + "failedAddPart": "Failed to add part!", + "failedAddPcb": "Failed to add pcb!", + "failedBomExport": "BOM export failed!", + "failedDeletedPartType": "Failed to delete part type {{name}}", + "failedAddedPartType": "Failed to add part type {{name}}", + "failedClearedCredentials": "Failed to clear cached credentials for {{apiName}}", + "testFailed": "Test failed", + "invalidOrder": "Hmmm, that doesn't look like a valid order number!", + "invalid2dBarcode": "Hmmm, I don't recognize that 2D barcode!" + }, + "success": { + "producedPcbs": "{{quantity}} PCB's were produced!", + "bomExported": "BOM exported successfully!", + "savedPartType": "Saved part type {{name}}", + "deletedPartType": "Deleted part type {{name}}", + "barcodeTypeReceived": "Barcode type {{type}} received", + "systemSettingsSaved": "System settings were saved.", + "clearedCredentials": "Successfully cleared cached credentials for {{apiName}}", + "testPassed": "Test passed", + "partsImported": "{{count}} parts were imported!", + "deletedProject": "Project was deleted!", + "deletedPcb": "Pcb was deleted!" + }, + "popup": { + "bom": { + "addPart": "Add a part to the BOM", + "downloadBom": "Download a BOM part list", + "removePart": "Remove selected parts from the BOM", + "addPcb": "Add a PCB to this project", + "producePcb": "Reduce inventory quantities when a PCB is assembled", + "filterOutOfStock": "Select to only show out of stock parts", + "pcb": "Indicates which PCB your part is assigned to. A PCB assignment is optional, all unassigned parts will appear in the <1>All tab.", + "quantity": "The quantity of parts required for a single PCB", + "inventoryQuantity": "The quantity of parts currently in inventory", + "referenceId": "Your custom <1 />reference Id(s) you can use for identifying this part.", + "schematicReferenceId": "Your custom <1 />schematic reference Id(s) that identify the part on the PCB silkscreen. <3/>Examples: <5>D1, <7>C2, <9>Q1", + "displayAll": "Displays all parts including parts not associated with a PCB.", + "unassociatedPartName": "Edit the name of your unassociated <1 /> part", + "bomQuantity": "Edit the <1 />BOM quantity required", + "bomCost": "Edit the part cost", + "quantityAvailable": "Edit the quantity available in your <1 />inventory", + "editReferenceId": "Edit your custom <1 />reference Id(s) you can use for identifying this part.", + "editSchematicReferenceId": "Edit your custom <1 />schematic reference Id(s) that identify the part on the PCB silkscreen. <3 />Examples: <5>D1, <7>C2, <9>Q1", + "customDescription": "Provide a custom description", + "customNote": "Provide a custom note" + } + }, + "footer": { + "version": "Version", + "promo": "Try <1>Binner Cloud Free" + }, + "notification": { + "versionBanner": { + "newVersion": "A new version of Binner <1>{{version}} is available!", + "releaseNotes": "Release Notes", + "view": "View", + "skip": "Skip", + "close": "Close" + } + }, + "color": { + "black": "Black", + "brown": "Brown", + "red": "Red", + "orange": "Orange", + "yellow": "Yellow", + "green": "Green", + "blue": "Blue", + "violet": "Violet", + "grey": "Grey", + "white": "White", + "gold": "Gold", + "silver": "Silver" + } } \ No newline at end of file diff --git a/Binner/Binner.Web/ClientApp/src/common/Utils.js b/Binner/Binner.Web/ClientApp/src/common/Utils.js index eecfa584..fd14c610 100644 --- a/Binner/Binner.Web/ClientApp/src/common/Utils.js +++ b/Binner/Binner.Web/ClientApp/src/common/Utils.js @@ -46,10 +46,22 @@ export const decodeResistance = (str) => { * @returns formatted number string */ export const formatCurrency = (number, currency = 'USD', maxDecimals = 5) => { - const lang = getLocaleLanguage(); - return number.toLocaleString(lang, { style: "currency", currency: currency, maximumFractionDigits: maxDecimals }); + if (!number || typeof number !== "number") number = 0; + const locale = getLocaleLanguage(); + return number.toLocaleString(locale, { style: "currency", currency: currency, maximumFractionDigits: maxDecimals }); }; +/** + * Get the currency symbol + * @param {string} currency Currency to use, default: 'USD' + * @returns formatted number string + */ +export const getCurrencySymbol = (currency = 'USD') => { + const locale = getLocaleLanguage(); + return (0).toLocaleString(locale, { style: 'currency', currency, minimumFractionDigits: 0, maximumFractionDigits: 0 }).replace(/\d/g, '').trim(); +}; + + /** * Get the locale language of the browser * @returns language string @@ -68,7 +80,6 @@ export const getLocaleLanguage = () => { * @returns formatted number string */ export const formatNumber = (number) => { - // return number.toString().replace(/\B(?{p.binNumber2} : null) : {p.binNumber2}} )}} } {col.cost && {(className, renderChildren) => { return ( - {renderChildren ? "$" + p.cost.toFixed(2) : null} + {renderChildren ? formatCurrency(p.cost, p.currency || "USD") : null} )}} } {col.digikeypartnumber && {(className, renderChildren) => { return ( {renderChildren ? {p.digiKeyPartNumber} : null} diff --git a/Binner/Binner.Web/ClientApp/src/custom.css b/Binner/Binner.Web/ClientApp/src/custom.css index 1c9156fe..a06eb938 100644 --- a/Binner/Binner.Web/ClientApp/src/custom.css +++ b/Binner/Binner.Web/ClientApp/src/custom.css @@ -59,14 +59,21 @@ code { font-size: 0.8em; float: right; position: absolute; + top: 2px; right: 4px; z-index: 1000; + } .header .language-selection .ui.selection.dropdown { - padding: 0.5em 0.5em; min-width: 100px; width: 100px; + min-height: 2em; + padding: 0.5em 2.1em 0.5em 1em; +} + +.header .language-selection .ui.selection.dropdown .icon { + margin: -1em; } .header .language-selection .ui.selection.dropdown .menu { @@ -305,8 +312,18 @@ section.formHeader { cursor: pointer; } +.ui.input.labeled.inline-editable .ui.label{ + background-color: #fff; + padding: 2px 1px; + margin-right: 1px; +} + .ui.input.inline-editable>input { - padding: 4px !important; + padding: 0 2px !important; +} + +.ui.input.labeled.inline-editable>input { + padding: 0 0 0 1px !important; } .ui.input.inline-editable>input:hover { diff --git a/Binner/Binner.Web/ClientApp/src/i18n.js b/Binner/Binner.Web/ClientApp/src/i18n.js index 315b3d60..ea3385da 100644 --- a/Binner/Binner.Web/ClientApp/src/i18n.js +++ b/Binner/Binner.Web/ClientApp/src/i18n.js @@ -3,6 +3,12 @@ import { initReactI18next } from 'react-i18next'; import Backend from 'i18next-http-backend'; import LanguageDetector from 'i18next-browser-languagedetector'; +const detectionOptions = { + order: ['querystring', 'cookie', 'localStorage', 'sessionStorage', 'navigator', 'htmlTag', 'path', 'subdomain'], + lookupQuerystring: 'lng', + caches: ['localStorage'], +}; + i18n // i18next-http-backend // loads translations from your server @@ -25,7 +31,9 @@ i18n }, backend: { loadPath: '/locales/{{lng}}/translation.json', - } + }, + detection: detectionOptions, + saveMissing: true }); export default i18n; \ No newline at end of file diff --git a/Binner/Binner.Web/ClientApp/src/layouts/Header.js b/Binner/Binner.Web/ClientApp/src/layouts/Header.js index 6b647632..b000f06f 100644 --- a/Binner/Binner.Web/ClientApp/src/layouts/Header.js +++ b/Binner/Binner.Web/ClientApp/src/layouts/Header.js @@ -1,38 +1,40 @@ import React, { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { NavMenu } from "./NavMenu"; -import { Dropdown, Icon } from "semantic-ui-react"; +import { Dropdown, Icon, Flag } from "semantic-ui-react"; // import i18n import '../i18n'; const lngs = { - en: { nativeName: 'English' }, // english - de: { nativeName: 'Deutsch' }, // german - fr: { nativeName: 'Français ' }, // french - es: { nativeName: 'Español' }, // spanish - zh: { nativeName: '中文' }, // chinese + en: { nativeName: 'English', flag: 'gb' }, // english + // temporary: enable languages as the translations are finished. + //de: { nativeName: 'Deutsch', flag: 'de' }, // german + //fr: { nativeName: 'Français ', flag: 'fr' }, // french + //es: { nativeName: 'Español', flag: 'mx' }, // spanish + //zh: { nativeName: '中文', flag: 'cn' }, // chinese }; export function Header() { - const { t, i18n } = useTranslation(); - const [language, setLanguage] = useState(localStorage.getItem('language') || i18n.resolvedLanguage || 'en'); + const { i18n } = useTranslation(); + // console.log('resolved langauge', i18n.resolvedLanguage); + const [language, setLanguage] = useState(i18n.resolvedLanguage || 'en'); useEffect(() => { - // console.log('init', localStorage.getItem('language'), i18n.resolvedLanguage); - setLanguage(localStorage.getItem('language') || i18n.resolvedLanguage || 'en'); + setLanguage(i18n.resolvedLanguage || 'en'); }, []); const langOptions = Object.keys(lngs).map((lng, key) => ({ key, text: lngs[lng].nativeName, value: lng, + content: {lngs[lng].nativeName} })); const handleChange = (e, control) => { e.preventDefault(); setLanguage(control.value); - localStorage.setItem('language', control.value); + // localStorage.setItem('i18nextLng', control.value); i18n.changeLanguage(control.value); }; @@ -42,6 +44,7 @@ export function Header() { { if (!isDirty) return; setLoading(true); - const request = { ...bomPart, + const request = { ...bomPart, + cost: parseFloat(bomPart.cost) || 0, quantity: parseInt(bomPart.quantity) || 0, quantityAvailable: bomPart.part ? 0 : (parseInt(bomPart.quantityAvailable) || 0), // conditionally add the part if it's available ...(bomPart.part && {part: { ...bomPart.part, - quantity: parseInt(bomPart.part.quantity) || 0 + cost: parseFloat(bomPart.part.cost) || 0, + quantity: parseInt(bomPart.part.quantity) || 0, } }) }; @@ -266,21 +268,28 @@ export function Bom(props) { case "quantity": parsed = parseInt(control.value); if (!isNaN(parsed)) { - part[control.name] = parsed; + part.quantity = parsed; } break; case "quantityAvailable": parsed = parseInt(control.value); if (!isNaN(parsed)) { // special case, if editing a part change its quantity. - /// if no part is associated, set the quantityAvailable + /// if no part is associated, set the custom quantityAvailable if (part.part) { - part.part['quantity'] = parsed; + part.part.quantity = parsed; } else { - part[control.name] = parsed; + part.quantityAvailable = parsed; } } break; + case "cost": + if (part.part) { + part.part.cost = control.value; + } else { + part.cost = control.value; + } + break; default: part[control.name] = control.value; break; @@ -543,7 +552,7 @@ export function Bom(props) {

} trigger={{t('label.pcb', "PCB")}} /> } - + {t('label.partNumber', "Part Number")} @@ -552,13 +561,13 @@ export function Bom(props) { {t('label.partType', "Part Type")} - + {t('label.cost', "Cost")} {t('popup.bom.quantity', "The quantity of parts required for a single PCB")}

} trigger={{t('label.quantity', "Quantity")}} />
- + {t('popup.bom.inventoryQuantity', "The quantity of parts currently in inventory")}

} trigger={{t('label.inStock', "In Stock")}} />
@@ -632,7 +641,29 @@ export function Bom(props) {
{bomPart.part?.manufacturerPartNumber && <> {bomPart.part?.manufacturerPartNumber}} {bomPart.part?.partType} - {formatCurrency(bomPart.part?.cost || 0)} + + + + Edit the part cost + +

} + trigger={ saveColumn(e, bomPart)} + onChange={(e, control) => handlePartsInlineChange(e, control, bomPart)} + value={bomPart.part?.cost || bomPart.cost || 0} + fluid + className="inline-editable" + />} + /> +
{ + /** + * Fetch the latest Binner version from GitHub + */ + const getLatestVersion = useCallback(async (installedVersion) => { const response = await fetch("system/version"); if (response.ok) { const latestVersionData = await response.json(); setVersionData(latestVersionData); const skipVersion = localStorage.getItem("skipVersion") || "1.0.0"; - if (semver.gt(latestVersionData.version, installedVersionData.version) && semver.gt(latestVersionData.version, skipVersion)) { + if (semver.gt(latestVersionData.version, installedVersion) && semver.gt(latestVersionData.version, skipVersion)) { // new version is available, and hasn't been skipped setNewVersionBannerIsOpen(true); } } }, []); + /** + * When we receive a version header from an api request, update it. + * This callback will be called on all updates to version + */ const updateVersion = useCallback((installedVersionData, subscriptionId) => { setVersion(installedVersionData.version); - - getLatestVersion(installedVersionData); - - }, [getLatestVersion]); + }, []); useEffect(() => { setSubscription(customEvents.subscribe("version", (data, subscriptionId) => updateVersion(data, subscriptionId))); @@ -66,6 +71,11 @@ export function Home(props) { load(); }, [setLoading, setSummary]); + useEffect(() => { + // if we know the current version, fetch the latest version + if (version) getLatestVersion(version); + }, [version, getLatestVersion]); + const route = (e, url) => { e.preventDefault(); e.stopPropagation(); @@ -169,8 +179,7 @@ export function Home(props) { - - {(summary.partsCost || 0).toFixed(2)} + {formatCurrency(summary.partsCost, summary.currency, 2)} {t('page.home.value', "Value")} diff --git a/Binner/Binner.Web/ClientApp/src/pages/Inventory.css b/Binner/Binner.Web/ClientApp/src/pages/Inventory.css index 6a09ab1a..575f5feb 100644 --- a/Binner/Binner.Web/ClientApp/src/pages/Inventory.css +++ b/Binner/Binner.Web/ClientApp/src/pages/Inventory.css @@ -171,4 +171,22 @@ .suggested-part a { font-weight: 500; font-style: normal; +} + +.ui.dropdown.label.currency .menu { + width: 170px; +} + +.ui.dropdown.label.currency .item { + font-size: 0.9em; +} + +.ui.dropdown.label.currency .item.selected { + font-size: 0.9em; + color: #2185d0; +} + +.ui.dropdown.label.currency .item .description { + font-size: 0.8em; + font-weight: normal; } \ No newline at end of file diff --git a/Binner/Binner.Web/ClientApp/src/pages/Inventory.js b/Binner/Binner.Web/ClientApp/src/pages/Inventory.js index 633bfb8d..8a5bbab1 100644 --- a/Binner/Binner.Web/ClientApp/src/pages/Inventory.js +++ b/Binner/Binner.Web/ClientApp/src/pages/Inventory.js @@ -27,7 +27,8 @@ import { Menu, Placeholder, Flag, - Checkbox + Checkbox, + Dropdown } from "semantic-ui-react"; import Carousel from "react-bootstrap/Carousel"; import NumberPicker from "../components/NumberPicker"; @@ -36,13 +37,14 @@ import { ChooseAlternatePartModal } from "../components/ChooseAlternatePartModal import Dropzone from "../components/Dropzone"; import { ProjectColors } from "../common/Types"; import { fetchApi } from "../common/fetchApi"; -import { formatCurrency, formatNumber } from "../common/Utils"; +import { formatCurrency, formatNumber, getCurrencySymbol } from "../common/Utils"; import { toast } from "react-toastify"; import { getPartTypeId } from "../common/partTypes"; import axios from "axios"; import { StoredFileType } from "../common/StoredFileType"; -import { GetTypeName, GetTypeValue } from "../common/Types"; +import { GetTypeName, GetTypeValue, GetAdvancedTypeDropdown } from "../common/Types"; import { BarcodeScannerInput } from "../components/BarcodeScannerInput"; +import { Currencies } from "../common/currency"; import "./Inventory.css"; const ProductImageIntervalMs = 10 * 1000; @@ -160,6 +162,7 @@ export function Inventory(props) { const [isEditing, setIsEditing] = useState((part && part.partId > 0) || (props.params && props.params.partNumber !== undefined && props.params.partNumber.length > 0)); const [showAddPartSupplier, setShowAddPartSupplier] = useState(false); const [partSupplier, setPartSupplier] = useState(defaultPartSupplier); + const currencyOptions = GetAdvancedTypeDropdown(Currencies, true); // todo: find a better alternative, we shouldn't need to do this! const bulkScanIsOpenRef = useRef(); @@ -2029,7 +2032,7 @@ export function Inventory(props) { + > + + + {supplier.supplier} {supplier.supplierPartNumber} - {formatCurrency(supplier.cost)} + {formatCurrency(supplier.cost, supplier.currency)} {formatNumber(supplier.quantityAvailable)} {formatNumber(supplier.minimumOrderQuantity)} diff --git a/Binner/Binner.Web/ClientApp/src/pages/OrderImport.js b/Binner/Binner.Web/ClientApp/src/pages/OrderImport.js index 754ea886..9a741d07 100644 --- a/Binner/Binner.Web/ClientApp/src/pages/OrderImport.js +++ b/Binner/Binner.Web/ClientApp/src/pages/OrderImport.js @@ -325,7 +325,7 @@ export function OrderImport(props) { {p.partType} {p.supplierPartNumber} - {formatCurrency(p.cost)} + {formatCurrency(p.cost, p.currency || "USD")} {p.quantityAvailable} diff --git a/Binner/Binner.Web/Controllers/PartController.cs b/Binner/Binner.Web/Controllers/PartController.cs index 968ad4e9..13a0bc19 100644 --- a/Binner/Binner.Web/Controllers/PartController.cs +++ b/Binner/Binner.Web/Controllers/PartController.cs @@ -445,9 +445,10 @@ public async Task OrderImportPartsAsync(OrderImportPartsRequest r if (existingParts.Any()) { var existingPart = existingParts.First(); - // update quantity + // update quantity and cost existingPart.Quantity += commonPart.QuantityAvailable; existingPart.Cost = (decimal)commonPart.Cost; + existingPart.Currency = commonPart.Currency; existingPart = await _partService.UpdatePartAsync(existingPart); parts.Add(Mapper.Map(existingPart)); } diff --git a/Binner/Library/Binner.Common/Binner.Common.csproj b/Binner/Library/Binner.Common/Binner.Common.csproj index c66ecdb8..7a39c907 100644 --- a/Binner/Library/Binner.Common/Binner.Common.csproj +++ b/Binner/Library/Binner.Common/Binner.Common.csproj @@ -1,7 +1,7 @@  - 1.0.36 + 1.0.37 net7.0 win10-x64 win10-x64;linux-arm;linux-arm64;linux-x64;osx.10.12-x64;ubuntu.14.04-x64 diff --git a/Binner/Library/Binner.Common/Models/Requests/AddBomPartRequest.cs b/Binner/Library/Binner.Common/Models/Requests/AddBomPartRequest.cs index 9cfd5aed..e81d8d2b 100644 --- a/Binner/Library/Binner.Common/Models/Requests/AddBomPartRequest.cs +++ b/Binner/Library/Binner.Common/Models/Requests/AddBomPartRequest.cs @@ -12,6 +12,16 @@ public class AddBomPartRequest public int? PcbId { get; set; } + /// + /// Cost of part (for parts without a part match) + /// + public double Cost { get; set; } + + /// + /// Currency of part + /// + public string? Currency { get; set; } + /// /// Quantity of part required for BOM /// diff --git a/Binner/Library/Binner.Common/Models/Requests/PartBase.cs b/Binner/Library/Binner.Common/Models/Requests/PartBase.cs index 21aaa0fc..94e70c7b 100644 --- a/Binner/Library/Binner.Common/Models/Requests/PartBase.cs +++ b/Binner/Library/Binner.Common/Models/Requests/PartBase.cs @@ -27,6 +27,11 @@ public class PartBase : IPreventDuplicateResource /// public decimal Cost { get; set; } + /// + /// Currency of part cost + /// + public string? Currency { get; set; } + /// /// Project Id /// diff --git a/Binner/Library/Binner.Common/Models/Requests/UpdateBomPartRequest.cs b/Binner/Library/Binner.Common/Models/Requests/UpdateBomPartRequest.cs index c18e0f62..07fac532 100644 --- a/Binner/Library/Binner.Common/Models/Requests/UpdateBomPartRequest.cs +++ b/Binner/Library/Binner.Common/Models/Requests/UpdateBomPartRequest.cs @@ -1,7 +1,4 @@ -using System.ComponentModel.DataAnnotations; -using System; - -namespace Binner.Common.Models.Requests +namespace Binner.Common.Models.Requests { public class UpdateBomPartRequest { @@ -30,6 +27,16 @@ public class UpdateBomPartRequest /// public string? PartName { get; set; } + /// + /// The cost of the part + /// + public double Cost { get; set; } + + /// + /// Currency of part + /// + public string? Currency { get; set; } + /// /// The quantity of parts needed (BOM) /// diff --git a/Binner/Library/Binner.Common/Models/Responses/PartResponse.cs b/Binner/Library/Binner.Common/Models/Responses/PartResponse.cs index dd080917..bd4c62b1 100644 --- a/Binner/Library/Binner.Common/Models/Responses/PartResponse.cs +++ b/Binner/Library/Binner.Common/Models/Responses/PartResponse.cs @@ -27,6 +27,11 @@ public class PartResponse /// public decimal Cost { get; set; } + /// + /// Currency of part cost + /// + public string? Currency { get; set; } + /// /// Project Id /// diff --git a/Binner/Library/Binner.Common/Models/Responses/ProjectPart.cs b/Binner/Library/Binner.Common/Models/Responses/ProjectPart.cs index 127af911..bf8d2987 100644 --- a/Binner/Library/Binner.Common/Models/Responses/ProjectPart.cs +++ b/Binner/Library/Binner.Common/Models/Responses/ProjectPart.cs @@ -31,6 +31,16 @@ public class ProjectPart /// public string? PartName { get; set; } + /// + /// The cost of the part + /// + public double Cost { get; set; } + + /// + /// Currency of part cost + /// + public string? Currency { get; set; } + /// /// The quantity of parts needed (BOM) /// diff --git a/Binner/Library/Binner.Common/Services/ProjectService.cs b/Binner/Library/Binner.Common/Services/ProjectService.cs index 0c3bde78..e9524b6b 100644 --- a/Binner/Library/Binner.Common/Services/ProjectService.cs +++ b/Binner/Library/Binner.Common/Services/ProjectService.cs @@ -141,6 +141,8 @@ public async Task> GetPcbsAsync(long projectId) Notes = request.Notes, PartName = request.PartNumber, PcbId = request.PcbId, + Cost = request.Cost, + Currency = request.Currency, Quantity = request.Quantity, QuantityAvailable = part == null ? request.QuantityAvailable : 0, ReferenceId = request.ReferenceId, @@ -182,20 +184,34 @@ public async Task> GetPcbsAsync(long projectId) assignment.PartId = part?.PartId; assignment.Notes = request.Notes; assignment.ReferenceId = request.ReferenceId; + assignment.Cost = request.Cost; + assignment.Currency = request.Currency; assignment.Quantity = request.Quantity; assignment.CustomDescription = request.CustomDescription; assignment.SchematicReferenceId = request.SchematicReferenceId; if (part == null) + { assignment.QuantityAvailable = request.QuantityAvailable; + assignment.Cost = request.Cost; + assignment.Currency = request.Currency; + } else + { assignment.QuantityAvailable = 0; + assignment.Cost = 0; + assignment.Currency = null; + } + await _storageProvider.UpdateProjectPartAssignmentAsync(assignment, user); - // also update the part quantity if it has changed - if (request.Part != null && part != null && request.Part.Quantity >= 0 && request.Part.Quantity != part.Quantity) + // also update the part quantity and cost if it has changed + if (request.Part != null && part != null) { - part.Quantity = request.Part.Quantity; + if (request.Part.Cost != part.Cost) + part.Cost = request.Part.Cost; + if (request.Part.Quantity >= 0 && request.Part.Quantity != part.Quantity) + part.Quantity = request.Part.Quantity; await _storageProvider.UpdatePartAsync(part, user); } diff --git a/Binner/Library/Binner.Common/StorageProviders/BinnerFileStorageProvider.cs b/Binner/Library/Binner.Common/StorageProviders/BinnerFileStorageProvider.cs index 29f6a0f1..1ae10b7e 100644 --- a/Binner/Library/Binner.Common/StorageProviders/BinnerFileStorageProvider.cs +++ b/Binner/Library/Binner.Common/StorageProviders/BinnerFileStorageProvider.cs @@ -28,7 +28,7 @@ public class BinnerFileStorageProvider : IStorageProvider private readonly ManualResetEvent _quitEvent = new(false); private readonly Guid _instance = Guid.NewGuid(); private PrimaryKeyTracker _primaryKeyTracker = new PrimaryKeyTracker(new Dictionary()); - private IBinnerDb _db = new BinnerDbV6(); + private IBinnerDb _db = new BinnerDbV7(); private bool _isDirty; private Thread _ioThread = new Thread(new ThreadStart(() => { })); @@ -263,7 +263,7 @@ public async Task DeletePartAsync(Part part, IUserContext? userContext) try { part.UserId = userContext?.UserId; - var entity = (_db as BinnerDbV6)?.Parts + var entity = (_db as BinnerDbV7)?.Parts .FirstOrDefault(x => x.PartId == part.PartId && x.UserId == part.UserId); if (entity == null) return false; @@ -545,7 +545,7 @@ public async Task DeletePartTypeAsync(PartType partType, IUserContext? use try { partType.UserId = userContext?.UserId; - var entity = (_db as BinnerDbV6)?.PartTypes + var entity = (_db as BinnerDbV7)?.PartTypes .FirstOrDefault(x => x.PartTypeId == partType.PartTypeId && x.UserId == partType.UserId); if (entity == null) return false; @@ -572,7 +572,7 @@ public async Task DeleteProjectAsync(Project project, IUserContext? userCo try { project.UserId = userContext?.UserId; - var entity = (_db as BinnerDbV6)?.Projects + var entity = (_db as BinnerDbV7)?.Projects .FirstOrDefault(x => x.ProjectId == project.ProjectId && x.UserId == project.UserId); if (entity == null) return false; @@ -602,7 +602,7 @@ public async Task AddStoredFileAsync(StoredFile storedFile, IUserCon storedFile.UserId = userContext?.UserId; storedFile.StoredFileId = _primaryKeyTracker.GetNextKey(); - (_db as BinnerDbV6)?.StoredFiles.Add(storedFile); + (_db as BinnerDbV7)?.StoredFiles.Add(storedFile); _isDirty = true; } finally @@ -618,7 +618,7 @@ public async Task AddStoredFileAsync(StoredFile storedFile, IUserCon await _dataLock.WaitAsync(); try { - return ((_db as BinnerDbV6)?.StoredFiles)?.FirstOrDefault(x => x.StoredFileId.Equals(storedFileId) && x.UserId == userContext?.UserId); + return ((_db as BinnerDbV7)?.StoredFiles)?.FirstOrDefault(x => x.StoredFileId.Equals(storedFileId) && x.UserId == userContext?.UserId); } finally { @@ -632,7 +632,7 @@ public async Task AddStoredFileAsync(StoredFile storedFile, IUserCon await _dataLock.WaitAsync(); try { - return ((_db as BinnerDbV6)?.StoredFiles)?.FirstOrDefault(x => x.FileName.Equals(filename) && x.UserId == userContext?.UserId); + return ((_db as BinnerDbV7)?.StoredFiles)?.FirstOrDefault(x => x.FileName.Equals(filename) && x.UserId == userContext?.UserId); } finally { @@ -646,7 +646,7 @@ public async Task> GetStoredFilesAsync(long partId, Stor await _dataLock.WaitAsync(); try { - return (_db as BinnerDbV6)?.StoredFiles + return (_db as BinnerDbV7)?.StoredFiles .Where(x => x.PartId.Equals(partId)) .Where(x => fileType == null || x.StoredFileType.Equals(fileType)) .Where(x => x.UserId == userContext?.UserId) @@ -664,7 +664,7 @@ public async Task> GetStoredFilesAsync(PaginatedRequest var pageRecords = (request.Page - 1) * request.Results; try { - return (_db as BinnerDbV6)?.StoredFiles + return (_db as BinnerDbV7)?.StoredFiles .Where(x => x.UserId == userContext?.UserId) .OrderBy(string.IsNullOrEmpty(request.OrderBy) ? "StoredFileId" : request.OrderBy, request.Direction) .Skip(pageRecords) @@ -683,14 +683,14 @@ public async Task DeleteStoredFileAsync(StoredFile storedFile, IUserContex try { storedFile.UserId = userContext?.UserId; - var entity = (_db as BinnerDbV6)?.StoredFiles + var entity = (_db as BinnerDbV7)?.StoredFiles .FirstOrDefault(x => x.StoredFileId == storedFile.StoredFileId && x.UserId == storedFile.UserId); if (entity == null) return false; - var itemRemoved = (_db as BinnerDbV6)?.StoredFiles.Remove(entity); + var itemRemoved = (_db as BinnerDbV7)?.StoredFiles.Remove(entity); if (itemRemoved == true) { - var nextStoredFileId = ((_db as BinnerDbV6)?.StoredFiles.OrderByDescending(x => x.StoredFileId) + var nextStoredFileId = ((_db as BinnerDbV7)?.StoredFiles.OrderByDescending(x => x.StoredFileId) .Select(x => x.StoredFileId) .FirstOrDefault() ?? 0); nextStoredFileId++; @@ -712,7 +712,7 @@ public async Task UpdateStoredFileAsync(StoredFile storedFile, IUser try { storedFile.UserId = userContext?.UserId; - var existingStoredFile = ((_db as BinnerDbV6)?.StoredFiles) + var existingStoredFile = ((_db as BinnerDbV7)?.StoredFiles) ?.FirstOrDefault(x => x.StoredFileId.Equals(storedFile.StoredFileId) && x.UserId == userContext?.UserId); if (existingStoredFile != null) { @@ -745,7 +745,7 @@ public async Task UpdateStoredFileAsync(StoredFile storedFile, IUser AuthorizationReceived = false }; - (_db as BinnerDbV6)?.OAuthRequests.Add(oAuthRequest); + (_db as BinnerDbV7)?.OAuthRequests.Add(oAuthRequest); _isDirty = true; } finally @@ -773,7 +773,7 @@ public async Task UpdateStoredFileAsync(StoredFile storedFile, IUser UserId = userContext?.UserId }; - var existingOAuthRequest = ((_db as BinnerDbV6)?.OAuthRequests) + var existingOAuthRequest = ((_db as BinnerDbV7)?.OAuthRequests) ?.FirstOrDefault(x => x.UserId == userContext?.UserId && x.Provider == oAuthRequest.Provider && x.RequestId == oAuthRequest.RequestId); if (existingOAuthRequest != null) { @@ -799,7 +799,7 @@ public async Task UpdateStoredFileAsync(StoredFile storedFile, IUser await _dataLock.WaitAsync(); try { - var existingOAuthRequest = ((_db as BinnerDbV6)?.OAuthRequests) + var existingOAuthRequest = ((_db as BinnerDbV7)?.OAuthRequests) ?.FirstOrDefault(x => x.RequestId.Equals(requestId) && x.UserId == userContext?.UserId); if (existingOAuthRequest == null) return null; @@ -828,7 +828,7 @@ public async Task UpdateStoredFileAsync(StoredFile storedFile, IUser await _dataLock.WaitAsync(); try { - return ((_db as BinnerDbV6)?.Pcbs)?.FirstOrDefault(x => x.PcbId == pcbId && x.UserId == userContext?.UserId); + return ((_db as BinnerDbV7)?.Pcbs)?.FirstOrDefault(x => x.PcbId == pcbId && x.UserId == userContext?.UserId); } finally { @@ -842,13 +842,13 @@ public async Task> GetPcbsAsync(long projectId, IUserContext? u try { var pcbs = new List(); - var assignments = (_db as BinnerDbV6)?.ProjectPcbAssignments + var assignments = (_db as BinnerDbV7)?.ProjectPcbAssignments .Where(x => x.ProjectId == projectId && x.UserId == userContext?.UserId) .ToList() ?? new List(); foreach (var assignment in assignments) { - var pcb = (_db as BinnerDbV6)?.Pcbs.FirstOrDefault(x => x.PcbId == assignment.PcbId); + var pcb = (_db as BinnerDbV7)?.Pcbs.FirstOrDefault(x => x.PcbId == assignment.PcbId); if (pcb != null) pcbs.Add(pcb); } @@ -868,7 +868,7 @@ public async Task AddPcbAsync(Pcb pcb, IUserContext? userContext) pcb.UserId = userContext?.UserId; pcb.PcbId = _primaryKeyTracker.GetNextKey(); - (_db as BinnerDbV6)?.Pcbs.Add(pcb); + (_db as BinnerDbV7)?.Pcbs.Add(pcb); _isDirty = true; } finally @@ -885,7 +885,7 @@ public async Task UpdatePcbAsync(Pcb pcb, IUserContext? userContext) try { pcb.UserId = userContext?.UserId; - var existingPcb = ((_db as BinnerDbV6)?.Pcbs) + var existingPcb = ((_db as BinnerDbV7)?.Pcbs) ?.FirstOrDefault(x => x.PcbId.Equals(pcb.PcbId) && x.UserId == userContext?.UserId); if (existingPcb != null) { @@ -909,15 +909,15 @@ public async Task DeletePcbAsync(Pcb pcb, IUserContext? userContext) { pcb.UserId = userContext?.UserId; // first get the full entity - var pcbEntity = (_db as BinnerDbV6)?.Pcbs + var pcbEntity = (_db as BinnerDbV7)?.Pcbs .FirstOrDefault(x => x.PcbId == pcb.PcbId && x.UserId == pcb.UserId); if (pcbEntity == null) return false; - var itemRemoved = (_db as BinnerDbV6)?.Pcbs.Remove(pcbEntity); + var itemRemoved = (_db as BinnerDbV7)?.Pcbs.Remove(pcbEntity); if (itemRemoved == true) { - var nextPcbId = ((_db as BinnerDbV6)?.Pcbs.OrderByDescending(x => x.PcbId) + var nextPcbId = ((_db as BinnerDbV7)?.Pcbs.OrderByDescending(x => x.PcbId) .Select(x => x.PcbId) .FirstOrDefault() ?? 0); nextPcbId++; @@ -937,7 +937,7 @@ public async Task DeletePcbAsync(Pcb pcb, IUserContext? userContext) await _dataLock.WaitAsync(); try { - return ((_db as BinnerDbV6)?.PcbStoredFileAssignments)?.FirstOrDefault(x => x.PcbStoredFileAssignmentId == pcbStoredFileAssignmentId && x.UserId == userContext?.UserId); + return ((_db as BinnerDbV7)?.PcbStoredFileAssignments)?.FirstOrDefault(x => x.PcbStoredFileAssignmentId == pcbStoredFileAssignmentId && x.UserId == userContext?.UserId); } finally { @@ -950,7 +950,7 @@ public async Task> GetPcbStoredFileAssignme await _dataLock.WaitAsync(); try { - return (_db as BinnerDbV6)?.PcbStoredFileAssignments + return (_db as BinnerDbV7)?.PcbStoredFileAssignments .Where(x => x.PcbId == pcbId && x.UserId == userContext?.UserId) .ToList() ?? new List(); } @@ -968,7 +968,7 @@ public async Task AddPcbStoredFileAssignmentAsync(PcbSt assignment.UserId = userContext?.UserId; assignment.PcbStoredFileAssignmentId = _primaryKeyTracker.GetNextKey(); - (_db as BinnerDbV6)?.PcbStoredFileAssignments.Add(assignment); + (_db as BinnerDbV7)?.PcbStoredFileAssignments.Add(assignment); _isDirty = true; } finally @@ -985,7 +985,7 @@ public async Task UpdatePcbStoredFileAssignmentAsync(Pc try { assignment.UserId = userContext?.UserId; - var existingPcbStoredFileAssignment = ((_db as BinnerDbV6)?.PcbStoredFileAssignments) + var existingPcbStoredFileAssignment = ((_db as BinnerDbV7)?.PcbStoredFileAssignments) ?.FirstOrDefault(x => x.PcbStoredFileAssignmentId.Equals(assignment.PcbStoredFileAssignmentId) && x.UserId == userContext?.UserId); if (existingPcbStoredFileAssignment != null) { @@ -1009,17 +1009,17 @@ public async Task RemovePcbStoredFileAssignmentAsync(PcbStoredFileAssignme { assignment.UserId = userContext?.UserId; // first get the full entity - var assignmentEntity = (_db as BinnerDbV6)?.PcbStoredFileAssignments + var assignmentEntity = (_db as BinnerDbV7)?.PcbStoredFileAssignments .FirstOrDefault(x => assignment.PcbStoredFileAssignmentId > 0 ? x.PcbStoredFileAssignmentId == assignment.PcbStoredFileAssignmentId : (x.PcbId == assignment.PcbId && x.StoredFileId == assignment.StoredFileId) && x.UserId == assignment.UserId); if (assignmentEntity == null) return false; - var itemRemoved = (_db as BinnerDbV6)?.PcbStoredFileAssignments.Remove(assignmentEntity); + var itemRemoved = (_db as BinnerDbV7)?.PcbStoredFileAssignments.Remove(assignmentEntity); if (itemRemoved == true) { - var nextPcbStoredFileAssignmentId = ((_db as BinnerDbV6)?.PcbStoredFileAssignments.OrderByDescending(x => x.PcbStoredFileAssignmentId) + var nextPcbStoredFileAssignmentId = ((_db as BinnerDbV7)?.PcbStoredFileAssignments.OrderByDescending(x => x.PcbStoredFileAssignmentId) .Select(x => x.PcbStoredFileAssignmentId) .FirstOrDefault() ?? 0); nextPcbStoredFileAssignmentId++; @@ -1039,7 +1039,7 @@ public async Task RemovePcbStoredFileAssignmentAsync(PcbStoredFileAssignme await _dataLock.WaitAsync(); try { - return ((_db as BinnerDbV6)?.ProjectPartAssignments)?.FirstOrDefault(x => x.ProjectPartAssignmentId == projectPartAssignmentId && x.UserId == userContext?.UserId); + return ((_db as BinnerDbV7)?.ProjectPartAssignments)?.FirstOrDefault(x => x.ProjectPartAssignmentId == projectPartAssignmentId && x.UserId == userContext?.UserId); } finally { @@ -1052,7 +1052,7 @@ public async Task> GetPartAssignmentsAsync(lo await _dataLock.WaitAsync(); try { - return (_db as BinnerDbV6)?.ProjectPartAssignments + return (_db as BinnerDbV7)?.ProjectPartAssignments .Where(x => x.PartId == partId && x.UserId == userContext?.UserId) .ToList() ?? new List(); } @@ -1067,7 +1067,7 @@ public async Task> GetPartAssignmentsAsync(lo await _dataLock.WaitAsync(); try { - return ((_db as BinnerDbV6)?.ProjectPartAssignments)?.FirstOrDefault(x => x.ProjectId == projectId && x.PartId == partId && x.UserId == userContext?.UserId); + return ((_db as BinnerDbV7)?.ProjectPartAssignments)?.FirstOrDefault(x => x.ProjectId == projectId && x.PartId == partId && x.UserId == userContext?.UserId); } finally { @@ -1080,7 +1080,7 @@ public async Task> GetPartAssignmentsAsync(lo await _dataLock.WaitAsync(); try { - return ((_db as BinnerDbV6)?.ProjectPartAssignments)?.FirstOrDefault(x => x.ProjectId == projectId && x.PartName != null && x.PartName.Equals(partName, StringComparison.InvariantCultureIgnoreCase) && x.UserId == userContext?.UserId); + return ((_db as BinnerDbV7)?.ProjectPartAssignments)?.FirstOrDefault(x => x.ProjectId == projectId && x.PartName != null && x.PartName.Equals(partName, StringComparison.InvariantCultureIgnoreCase) && x.UserId == userContext?.UserId); } finally { @@ -1093,7 +1093,7 @@ public async Task> GetProjectPartAssignmentsA await _dataLock.WaitAsync(); try { - return (_db as BinnerDbV6)?.ProjectPartAssignments + return (_db as BinnerDbV7)?.ProjectPartAssignments .Where(x => x.ProjectId == projectId && x.UserId == userContext?.UserId) .ToList() ?? new List(); } @@ -1109,7 +1109,7 @@ public async Task> GetProjectPartAssignmentsA var pageRecords = (request.Page - 1) * request.Results; try { - return (_db as BinnerDbV6)?.ProjectPartAssignments + return (_db as BinnerDbV7)?.ProjectPartAssignments .Where(x => x.ProjectId == projectId && x.UserId == userContext?.UserId) .OrderBy(string.IsNullOrEmpty(request.OrderBy) ? "ProjectPartAssignmentId" : request.OrderBy, request.Direction) .Skip(pageRecords) @@ -1130,7 +1130,7 @@ public async Task AddProjectPartAssignmentAsync(ProjectPa assignment.UserId = userContext?.UserId; assignment.ProjectPartAssignmentId = _primaryKeyTracker.GetNextKey(); - (_db as BinnerDbV6)?.ProjectPartAssignments.Add(assignment); + (_db as BinnerDbV7)?.ProjectPartAssignments.Add(assignment); _isDirty = true; } finally @@ -1147,7 +1147,7 @@ public async Task UpdateProjectPartAssignmentAsync(Projec try { assignment.UserId = userContext?.UserId; - var existingProjectPartAssignment = ((_db as BinnerDbV6)?.ProjectPartAssignments) + var existingProjectPartAssignment = ((_db as BinnerDbV7)?.ProjectPartAssignments) ?.FirstOrDefault(x => x.ProjectPartAssignmentId.Equals(assignment.ProjectPartAssignmentId) && x.UserId == userContext?.UserId); if (existingProjectPartAssignment != null) { @@ -1171,17 +1171,17 @@ public async Task RemoveProjectPartAssignmentAsync(ProjectPartAssignment a { assignment.UserId = userContext?.UserId; // first get the full entity - var assignmentEntity = (_db as BinnerDbV6)?.ProjectPartAssignments + var assignmentEntity = (_db as BinnerDbV7)?.ProjectPartAssignments .FirstOrDefault(x => assignment.ProjectPartAssignmentId > 0 ? x.ProjectPartAssignmentId == assignment.ProjectPartAssignmentId : (x.ProjectId == assignment.ProjectId && x.PcbId == assignment.PcbId) && x.UserId == assignment.UserId); if (assignmentEntity == null) return false; - var itemRemoved = (_db as BinnerDbV6)?.ProjectPartAssignments.Remove(assignmentEntity); + var itemRemoved = (_db as BinnerDbV7)?.ProjectPartAssignments.Remove(assignmentEntity); if (itemRemoved == true) { - var nextProjectPartAssignmentId = ((_db as BinnerDbV6)?.ProjectPartAssignments.OrderByDescending(x => x.ProjectPartAssignmentId) + var nextProjectPartAssignmentId = ((_db as BinnerDbV7)?.ProjectPartAssignments.OrderByDescending(x => x.ProjectPartAssignmentId) .Select(x => x.ProjectPartAssignmentId) .FirstOrDefault() ?? 0); nextProjectPartAssignmentId++; @@ -1201,7 +1201,7 @@ public async Task RemoveProjectPartAssignmentAsync(ProjectPartAssignment a await _dataLock.WaitAsync(); try { - return ((_db as BinnerDbV6)?.ProjectPcbAssignments)?.FirstOrDefault(x => x.ProjectPcbAssignmentId == projectPcbAssignmentId && x.UserId == userContext?.UserId); + return ((_db as BinnerDbV7)?.ProjectPcbAssignments)?.FirstOrDefault(x => x.ProjectPcbAssignmentId == projectPcbAssignmentId && x.UserId == userContext?.UserId); } finally { @@ -1214,7 +1214,7 @@ public async Task> GetProjectPcbAssignmentsAsy await _dataLock.WaitAsync(); try { - return (_db as BinnerDbV6)?.ProjectPcbAssignments + return (_db as BinnerDbV7)?.ProjectPcbAssignments .Where(x => x.ProjectId == projectId && x.UserId == userContext?.UserId) .ToList() ?? new List(); } @@ -1232,7 +1232,7 @@ public async Task AddProjectPcbAssignmentAsync(ProjectPcbA assignment.UserId = userContext?.UserId; assignment.ProjectPcbAssignmentId = _primaryKeyTracker.GetNextKey(); - (_db as BinnerDbV6)?.ProjectPcbAssignments.Add(assignment); + (_db as BinnerDbV7)?.ProjectPcbAssignments.Add(assignment); _isDirty = true; } finally @@ -1249,7 +1249,7 @@ public async Task UpdateProjectPcbAssignmentAsync(ProjectP try { assignment.UserId = userContext?.UserId; - var existingProjectPcbAssignment = ((_db as BinnerDbV6)?.ProjectPcbAssignments) + var existingProjectPcbAssignment = ((_db as BinnerDbV7)?.ProjectPcbAssignments) ?.FirstOrDefault(x => x.ProjectPcbAssignmentId.Equals(assignment.ProjectPcbAssignmentId) && x.UserId == userContext?.UserId); if (existingProjectPcbAssignment != null) { @@ -1272,17 +1272,17 @@ public async Task RemoveProjectPcbAssignmentAsync(ProjectPcbAssignment ass { assignment.UserId = userContext?.UserId; // first get the full entity - var assignmentEntity = (_db as BinnerDbV6)?.ProjectPcbAssignments + var assignmentEntity = (_db as BinnerDbV7)?.ProjectPcbAssignments .FirstOrDefault(x => assignment.ProjectPcbAssignmentId > 0 ? x.ProjectPcbAssignmentId == assignment.ProjectPcbAssignmentId : (x.ProjectId == assignment.ProjectId && x.PcbId == assignment.PcbId) && x.UserId == assignment.UserId); if (assignmentEntity == null) return false; - var itemRemoved = (_db as BinnerDbV6)?.ProjectPcbAssignments.Remove(assignmentEntity); + var itemRemoved = (_db as BinnerDbV7)?.ProjectPcbAssignments.Remove(assignmentEntity); if (itemRemoved == true) { - var nextProjectPcbAssignmentId = ((_db as BinnerDbV6)?.ProjectPcbAssignments.OrderByDescending(x => x.ProjectPcbAssignmentId) + var nextProjectPcbAssignmentId = ((_db as BinnerDbV7)?.ProjectPcbAssignments.OrderByDescending(x => x.ProjectPcbAssignmentId) .Select(x => x.ProjectPcbAssignmentId) .FirstOrDefault() ?? 0); nextProjectPcbAssignmentId++; @@ -1309,7 +1309,7 @@ public async Task AddPartSupplierAsync(PartSupplier partSupplier, partSupplier.UserId = userContext?.UserId; partSupplier.PartSupplierId = _primaryKeyTracker.GetNextKey(); - (_db as BinnerDbV6)?.PartSuppliers.Add(partSupplier); + (_db as BinnerDbV7)?.PartSuppliers.Add(partSupplier); _isDirty = true; } finally @@ -1326,7 +1326,7 @@ public async Task AddPartSupplierAsync(PartSupplier partSupplier, await _dataLock.WaitAsync(); try { - return ((_db as BinnerDbV6)?.PartSuppliers)?.FirstOrDefault(x => x.PartSupplierId.Equals(partSupplierId) && x.UserId == userContext?.UserId); + return ((_db as BinnerDbV7)?.PartSuppliers)?.FirstOrDefault(x => x.PartSupplierId.Equals(partSupplierId) && x.UserId == userContext?.UserId); } finally { @@ -1340,7 +1340,7 @@ public async Task> GetPartSuppliersAsync(long partId, await _dataLock.WaitAsync(); try { - return (_db as BinnerDbV6)?.PartSuppliers + return (_db as BinnerDbV7)?.PartSuppliers .Where(x => x.PartId.Equals(partId)) .Where(x => x.UserId == userContext?.UserId) .ToList() ?? new List(); @@ -1358,7 +1358,7 @@ public async Task UpdatePartSupplierAsync(PartSupplier partSupplie try { partSupplier.UserId = userContext?.UserId; - var existingPartSupplier = ((_db as BinnerDbV6)?.PartSuppliers) + var existingPartSupplier = ((_db as BinnerDbV7)?.PartSuppliers) ?.FirstOrDefault(x => x.PartSupplierId.Equals(partSupplier.PartSupplierId) && x.UserId == userContext?.UserId); if (existingPartSupplier != null) { @@ -1380,14 +1380,14 @@ public async Task DeletePartSupplierAsync(PartSupplier partSupplier, IUser try { partSupplier.UserId = userContext?.UserId; - var entity = (_db as BinnerDbV6)?.PartSuppliers + var entity = (_db as BinnerDbV7)?.PartSuppliers .FirstOrDefault(x => x.PartSupplierId == partSupplier.PartSupplierId && x.UserId == partSupplier.UserId); if (entity == null) return false; - var itemRemoved = (_db as BinnerDbV6)?.PartSuppliers.Remove(entity); + var itemRemoved = (_db as BinnerDbV7)?.PartSuppliers.Remove(entity); if (itemRemoved == true) { - var nextPartSupplierId = ((_db as BinnerDbV6)?.PartSuppliers.OrderByDescending(x => x.PartSupplierId) + var nextPartSupplierId = ((_db as BinnerDbV7)?.PartSuppliers.OrderByDescending(x => x.PartSupplierId) .Select(x => x.PartSupplierId) .FirstOrDefault() ?? 0); nextPartSupplierId++; @@ -1537,13 +1537,13 @@ private async Task LoadDatabaseAsync(bool requireLock = true) { nameof(Part), Math.Max(_db.Parts.OrderByDescending(x => x.PartId).Select(x => x.PartId).FirstOrDefault() + 1, 1) }, { nameof(PartType), Math.Max(_db.PartTypes.OrderByDescending(x => x.PartTypeId).Select(x => x.PartTypeId).FirstOrDefault() + 1, 1) }, { nameof(Project), Math.Max(_db.Projects.OrderByDescending(x => x.ProjectId).Select(x => x.ProjectId).FirstOrDefault() + 1, 1) }, - { nameof(StoredFile), Math.Max((_db as BinnerDbV6)?.StoredFiles.OrderByDescending(x => x.StoredFileId).Select(x => x.StoredFileId).FirstOrDefault() ?? 0 + 1, 1) }, - { nameof(OAuthRequest), Math.Max((_db as BinnerDbV6)?.OAuthRequests.OrderByDescending(x => x.OAuthRequestId).Select(x => x.OAuthRequestId).FirstOrDefault() ?? 0 + 1, 1) }, - { nameof(Pcb), Math.Max((_db as BinnerDbV6)?.OAuthRequests.OrderByDescending(x => x.OAuthRequestId).Select(x => x.OAuthRequestId).FirstOrDefault() ?? 0 + 1, 1) }, - { nameof(PcbStoredFileAssignment), Math.Max((_db as BinnerDbV6)?.OAuthRequests.OrderByDescending(x => x.OAuthRequestId).Select(x => x.OAuthRequestId).FirstOrDefault() ?? 0 + 1, 1) }, - { nameof(ProjectPartAssignment), Math.Max((_db as BinnerDbV6)?.OAuthRequests.OrderByDescending(x => x.OAuthRequestId).Select(x => x.OAuthRequestId).FirstOrDefault() ?? 0 + 1, 1) }, - { nameof(ProjectPcbAssignment), Math.Max((_db as BinnerDbV6)?.OAuthRequests.OrderByDescending(x => x.OAuthRequestId).Select(x => x.OAuthRequestId).FirstOrDefault() ?? 0 + 1, 1) }, - { nameof(PartSupplier), Math.Max((_db as BinnerDbV6)?.PartSuppliers.OrderByDescending(x => x.PartSupplierId).Select(x => x.PartSupplierId).FirstOrDefault() ?? 0 + 1, 1) }, + { nameof(StoredFile), Math.Max((_db as BinnerDbV7)?.StoredFiles.OrderByDescending(x => x.StoredFileId).Select(x => x.StoredFileId).FirstOrDefault() ?? 0 + 1, 1) }, + { nameof(OAuthRequest), Math.Max((_db as BinnerDbV7)?.OAuthRequests.OrderByDescending(x => x.OAuthRequestId).Select(x => x.OAuthRequestId).FirstOrDefault() ?? 0 + 1, 1) }, + { nameof(Pcb), Math.Max((_db as BinnerDbV7)?.OAuthRequests.OrderByDescending(x => x.OAuthRequestId).Select(x => x.OAuthRequestId).FirstOrDefault() ?? 0 + 1, 1) }, + { nameof(PcbStoredFileAssignment), Math.Max((_db as BinnerDbV7)?.OAuthRequests.OrderByDescending(x => x.OAuthRequestId).Select(x => x.OAuthRequestId).FirstOrDefault() ?? 0 + 1, 1) }, + { nameof(ProjectPartAssignment), Math.Max((_db as BinnerDbV7)?.OAuthRequests.OrderByDescending(x => x.OAuthRequestId).Select(x => x.OAuthRequestId).FirstOrDefault() ?? 0 + 1, 1) }, + { nameof(ProjectPcbAssignment), Math.Max((_db as BinnerDbV7)?.OAuthRequests.OrderByDescending(x => x.OAuthRequestId).Select(x => x.OAuthRequestId).FirstOrDefault() ?? 0 + 1, 1) }, + { nameof(PartSupplier), Math.Max((_db as BinnerDbV7)?.PartSuppliers.OrderByDescending(x => x.PartSupplierId).Select(x => x.PartSupplierId).FirstOrDefault() ?? 0 + 1, 1) }, }); } } @@ -1587,17 +1587,19 @@ private IBinnerDb LoadDatabaseByVersion(BinnerDbVersion version, byte[] bytes) db = version.Version switch { // Version 1 (upgrade required) - BinnerDbV1.VersionNumber => new BinnerDbV6(_serializer.Deserialize(bytes, SerializationOptions, new PropertyVersion("BinnerDbV4", "BinnerDbV5", "BinnerDbV6")), (upgradeDb) => BuildChecksum(upgradeDb)), + BinnerDbV1.VersionNumber => new BinnerDbV7(_serializer.Deserialize(bytes, SerializationOptions, new PropertyVersion("BinnerDbV4", "BinnerDbV5", "BinnerDbV6", "BinnerDbV7")), (upgradeDb) => BuildChecksum(upgradeDb)), // Version 2 (upgrade required) - BinnerDbV2.VersionNumber => new BinnerDbV6(_serializer.Deserialize(bytes, SerializationOptions, new PropertyVersion("BinnerDbV4", "BinnerDbV5", "BinnerDbV6")), (upgradeDb) => BuildChecksum(upgradeDb)), + BinnerDbV2.VersionNumber => new BinnerDbV7(_serializer.Deserialize(bytes, SerializationOptions, new PropertyVersion("BinnerDbV4", "BinnerDbV5", "BinnerDbV6", "BinnerDbV7")), (upgradeDb) => BuildChecksum(upgradeDb)), // Version 3 (upgrade required) - BinnerDbV3.VersionNumber => new BinnerDbV6(_serializer.Deserialize(bytes, SerializationOptions, new PropertyVersion("BinnerDbV4", "BinnerDbV5", "BinnerDbV6")), (upgradeDb) => BuildChecksum(upgradeDb)), + BinnerDbV3.VersionNumber => new BinnerDbV7(_serializer.Deserialize(bytes, SerializationOptions, new PropertyVersion("BinnerDbV4", "BinnerDbV5", "BinnerDbV6", "BinnerDbV7")), (upgradeDb) => BuildChecksum(upgradeDb)), // Version 4 (upgrade required) - BinnerDbV4.VersionNumber => new BinnerDbV6(_serializer.Deserialize(bytes, SerializationOptions, new PropertyVersion("BinnerDbV5", "BinnerDbV6")), (upgradeDb) => BuildChecksum(upgradeDb)), + BinnerDbV4.VersionNumber => new BinnerDbV7(_serializer.Deserialize(bytes, SerializationOptions, new PropertyVersion("BinnerDbV5", "BinnerDbV6", "BinnerDbV7")), (upgradeDb) => BuildChecksum(upgradeDb)), // Version 5 (upgrade required) - BinnerDbV5.VersionNumber => new BinnerDbV6(_serializer.Deserialize(bytes, SerializationOptions, new PropertyVersion("BinnerDbV6")), (upgradeDb) => BuildChecksum(upgradeDb)), + BinnerDbV5.VersionNumber => new BinnerDbV7(_serializer.Deserialize(bytes, SerializationOptions, new PropertyVersion("BinnerDbV6", "BinnerDbV7")), (upgradeDb) => BuildChecksum(upgradeDb)), + // Version (upgrade required) + BinnerDbV6.VersionNumber => new BinnerDbV7(_serializer.Deserialize(bytes, SerializationOptions, new PropertyVersion("BinnerDbV7")), (upgradeDb) => BuildChecksum(upgradeDb)), // Current version - BinnerDbV6.VersionNumber => _serializer.Deserialize(bytes, SerializationOptions), + BinnerDbV7.VersionNumber => _serializer.Deserialize(bytes, SerializationOptions), _ => throw new InvalidOperationException($"Unsupported database version: {version}"), }; return db; @@ -1619,7 +1621,7 @@ private async Task SaveDatabaseAsync(bool requireLock = true) await _dataLock.WaitAsync(); try { - var db = _db as BinnerDbV6; + var db = _db as BinnerDbV7; if (db == null) return; db.FirstPartId = db.Parts @@ -1633,7 +1635,7 @@ private async Task SaveDatabaseAsync(bool requireLock = true) db.Count = db.Parts.Count; db.Checksum = BuildChecksum(db); using var stream = new MemoryStream(); - WriteDbVersion(stream, new BinnerDbVersion(BinnerDbV6.VersionNumber, BinnerDbV5.VersionCreated)); + WriteDbVersion(stream, new BinnerDbVersion(BinnerDbV7.VersionNumber, BinnerDbV7.VersionCreated)); var serializedBytes = _serializer.Serialize(db, SerializationOptions); stream.Write(serializedBytes, 0, serializedBytes.Length); var directoryName = Path.GetDirectoryName(_config.Filename); @@ -1707,7 +1709,7 @@ private IBinnerDb NewDatabase() }); } - return new BinnerDbV6 + return new BinnerDbV7 { Count = 0, FirstPartId = 0,