mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-11-04 07:05:41 +00:00 
			
		
		
		
	Now displaying item match table
This commit is contained in:
		@@ -20,8 +20,8 @@ class FileManager:
 | 
			
		||||
    # Fields which are absolutely necessary for valid upload
 | 
			
		||||
    REQUIRED_HEADERS = []
 | 
			
		||||
 | 
			
		||||
    # Fields which are used for part matching (only one of them is needed)
 | 
			
		||||
    PART_MATCH_HEADERS = []
 | 
			
		||||
    # Fields which are used for item matching (only one of them is needed)
 | 
			
		||||
    ITEM_MATCH_HEADERS = []
 | 
			
		||||
    
 | 
			
		||||
    # Fields which would be helpful but are not required
 | 
			
		||||
    OPTIONAL_HEADERS = []
 | 
			
		||||
@@ -83,7 +83,7 @@ class FileManager:
 | 
			
		||||
    def update_headers(self):
 | 
			
		||||
        """ Update headers """
 | 
			
		||||
 | 
			
		||||
        self.HEADERS = self.REQUIRED_HEADERS + self.PART_MATCH_HEADERS + self.OPTIONAL_HEADERS
 | 
			
		||||
        self.HEADERS = self.REQUIRED_HEADERS + self.ITEM_MATCH_HEADERS + self.OPTIONAL_HEADERS
 | 
			
		||||
            
 | 
			
		||||
    def setup(self):
 | 
			
		||||
        """ Setup headers depending on the file name """
 | 
			
		||||
@@ -96,7 +96,7 @@ class FileManager:
 | 
			
		||||
                'Quantity',
 | 
			
		||||
            ]
 | 
			
		||||
 | 
			
		||||
            self.PART_MATCH_HEADERS = [
 | 
			
		||||
            self.ITEM_MATCH_HEADERS = [
 | 
			
		||||
                'Manufacturer_MPN',
 | 
			
		||||
                'Supplier_SKU',
 | 
			
		||||
            ]
 | 
			
		||||
 
 | 
			
		||||
@@ -119,18 +119,40 @@ class MatchItem(forms.Form):
 | 
			
		||||
        # Item selection
 | 
			
		||||
        for row in row_data:
 | 
			
		||||
            for col in row['data']:
 | 
			
		||||
                print(f'{row["index"]=} | {col["column"]["guess"]=}')
 | 
			
		||||
                if col['column']['guess']:
 | 
			
		||||
                    if col['column']['guess'] in file_manager.PART_MATCH_HEADERS:
 | 
			
		||||
                if col['column']['guess'] in file_manager.REQUIRED_HEADERS:
 | 
			
		||||
                    field_name = col['column']['guess'].lower() + '-' + str(row['index'] - 1)
 | 
			
		||||
                    if 'quantity' in col['column']['guess'].lower():
 | 
			
		||||
                        self.fields[field_name] = forms.CharField(
 | 
			
		||||
                            required=True,
 | 
			
		||||
                            widget=forms.NumberInput(attrs={
 | 
			
		||||
                                'name': 'quantity' + str(row['index']),
 | 
			
		||||
                                'class': 'numberinput',
 | 
			
		||||
                                'type': 'number',
 | 
			
		||||
                                'min': '1',
 | 
			
		||||
                                'step': 'any',
 | 
			
		||||
                                'value': row['quantity'],
 | 
			
		||||
                            })
 | 
			
		||||
                        )
 | 
			
		||||
                    else:
 | 
			
		||||
                        self.fields[field_name] = forms.Input(
 | 
			
		||||
                            required=True,
 | 
			
		||||
                            widget=forms.Select(attrs={
 | 
			
		||||
                            })
 | 
			
		||||
                        )
 | 
			
		||||
                elif col['column']['guess'] in file_manager.ITEM_MATCH_HEADERS:
 | 
			
		||||
                    print(f'{row["index"]=} | {col["column"]["guess"]=} | {row.get("item_match", "No Match")}')
 | 
			
		||||
                    
 | 
			
		||||
                    # Get item options
 | 
			
		||||
                        item_options = row['item_options']
 | 
			
		||||
                    item_options = [(option.id, option) for option in row['item_options']]
 | 
			
		||||
                    # Get item match
 | 
			
		||||
                    item_match = row['item_match']
 | 
			
		||||
 | 
			
		||||
                        field_name = col['column']['guess'].lower() + '_' + str(row['index'])
 | 
			
		||||
                    field_name = col['column']['guess'].lower() + '-' + str(row['index'] - 1)
 | 
			
		||||
                    self.fields[field_name] = forms.ChoiceField(
 | 
			
		||||
                            choices=item_options,
 | 
			
		||||
                        choices=[('', '-' * 10)] + item_options,
 | 
			
		||||
                        required=True,
 | 
			
		||||
                        widget=forms.Select(attrs={'class': 'bomselect'})
 | 
			
		||||
                    )
 | 
			
		||||
                    if item_match:
 | 
			
		||||
                            self.fields[field_name].initial = item_match
 | 
			
		||||
                        print(f'{item_match=}')
 | 
			
		||||
                        self.fields[field_name].initial = item_match.id
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ from . import models
 | 
			
		||||
from . import forms
 | 
			
		||||
from .files import FileManager
 | 
			
		||||
 | 
			
		||||
from part.models import SupplierPart
 | 
			
		||||
from company.models import ManufacturerPart, SupplierPart
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SettingEdit(AjaxUpdateView):
 | 
			
		||||
@@ -195,12 +195,41 @@ class FileManagementFormView(MultiStepFormView):
 | 
			
		||||
    def get_context_data(self, form, **kwargs):
 | 
			
		||||
        context = super().get_context_data(form=form, **kwargs)
 | 
			
		||||
 | 
			
		||||
        if self.steps.current == 'fields':
 | 
			
		||||
        if self.steps.current == 'fields' or self.steps.current == 'items':
 | 
			
		||||
            # Get columns and row data
 | 
			
		||||
            columns = self.file_manager.columns()
 | 
			
		||||
            rows = self.file_manager.rows()
 | 
			
		||||
 | 
			
		||||
            
 | 
			
		||||
            key_item_select = ''
 | 
			
		||||
            key_quantity_select = ''
 | 
			
		||||
            if self.steps.current == 'items':
 | 
			
		||||
                # Get file manager
 | 
			
		||||
                self.getFileManager()
 | 
			
		||||
                # Find column key for item selection
 | 
			
		||||
                for item in self.file_manager.ITEM_MATCH_HEADERS:
 | 
			
		||||
                    item = item.lower()
 | 
			
		||||
                    for key in form.fields.keys():
 | 
			
		||||
                        print(f'{item=} is in {key=} ?')
 | 
			
		||||
                        if item in key:
 | 
			
		||||
                            key_item_select = item
 | 
			
		||||
                            break
 | 
			
		||||
                            break
 | 
			
		||||
 | 
			
		||||
                # Find column key for quantity selection
 | 
			
		||||
                key_quantity_select = 'quantity'
 | 
			
		||||
 | 
			
		||||
            # Optimize for template
 | 
			
		||||
            for row in rows:
 | 
			
		||||
                
 | 
			
		||||
                # Add item select field
 | 
			
		||||
                if key_item_select:
 | 
			
		||||
                    row['item_select'] = key_item_select + '-' + str(row['index'])
 | 
			
		||||
                    print(f'{row["item_select"]}')
 | 
			
		||||
                # Add quantity select field
 | 
			
		||||
                if key_quantity_select:
 | 
			
		||||
                    row['quantity_select'] = key_quantity_select + '-' + str(row['index'])
 | 
			
		||||
                    
 | 
			
		||||
                row_data = row['data']
 | 
			
		||||
 | 
			
		||||
                data = []
 | 
			
		||||
@@ -209,12 +238,16 @@ class FileManagementFormView(MultiStepFormView):
 | 
			
		||||
                    data.append({
 | 
			
		||||
                        'cell': item,
 | 
			
		||||
                        'idx': idx,
 | 
			
		||||
                        'column': columns[idx]
 | 
			
		||||
                        'column': columns[idx],
 | 
			
		||||
                    })
 | 
			
		||||
                
 | 
			
		||||
                row['data'] = data
 | 
			
		||||
 | 
			
		||||
                print(f'\n{row=}')
 | 
			
		||||
 | 
			
		||||
            context.update({'rows': rows})
 | 
			
		||||
            if self.steps.current == 'items':
 | 
			
		||||
                context.update({'columns': columns})
 | 
			
		||||
 | 
			
		||||
        # Load extra context data
 | 
			
		||||
        print(f'{self.extra_context_data=}')
 | 
			
		||||
@@ -393,14 +426,14 @@ class FileManagementFormView(MultiStepFormView):
 | 
			
		||||
 | 
			
		||||
        # Check that at least one of the part match field is present
 | 
			
		||||
        part_match_found = False
 | 
			
		||||
        for col in self.file_manager.PART_MATCH_HEADERS:
 | 
			
		||||
        for col in self.file_manager.ITEM_MATCH_HEADERS:
 | 
			
		||||
            if col in self.column_selections.values():
 | 
			
		||||
                part_match_found = True
 | 
			
		||||
                break
 | 
			
		||||
        
 | 
			
		||||
        # If not, notify user
 | 
			
		||||
        if not part_match_found:
 | 
			
		||||
            for col in self.file_manager.PART_MATCH_HEADERS:
 | 
			
		||||
            for col in self.file_manager.ITEM_MATCH_HEADERS:
 | 
			
		||||
                missing_columns.append(col)
 | 
			
		||||
 | 
			
		||||
        # Store extra context data
 | 
			
		||||
@@ -425,15 +458,16 @@ class FileManagementFormView(MultiStepFormView):
 | 
			
		||||
        The pre-fill data are then passed through to the part selection form.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        match_supplier = False
 | 
			
		||||
        match_manufacturer = False
 | 
			
		||||
 | 
			
		||||
        # Fields prefixed with "Part_" can be used to do "smart matching" against Part objects in the database
 | 
			
		||||
        q_idx = self.getColumnIndex('Quantity')
 | 
			
		||||
        s_idx = self.getColumnIndex('Supplier_SKU')
 | 
			
		||||
        # m_idx = self.getColumnIndex('Manufacturer_MPN')
 | 
			
		||||
        m_idx = self.getColumnIndex('Manufacturer_MPN')
 | 
			
		||||
        # p_idx = self.getColumnIndex('Unit_Price')
 | 
			
		||||
        # e_idx = self.getColumnIndex('Extended_Price')
 | 
			
		||||
 | 
			
		||||
        self.allowed_items = SupplierPart.objects.all()
 | 
			
		||||
 | 
			
		||||
        for row in self.rows:
 | 
			
		||||
 | 
			
		||||
            # Initially use a quantity of zero
 | 
			
		||||
@@ -442,9 +476,6 @@ class FileManagementFormView(MultiStepFormView):
 | 
			
		||||
            # Initially we do not have a part to reference
 | 
			
		||||
            exact_match_part = None
 | 
			
		||||
 | 
			
		||||
            # A list of potential Part matches
 | 
			
		||||
            item_options = self.allowed_items
 | 
			
		||||
 | 
			
		||||
            # Check if there is a column corresponding to "quantity"
 | 
			
		||||
            if q_idx >= 0:
 | 
			
		||||
                q_val = row['data'][q_idx]['cell']
 | 
			
		||||
@@ -464,7 +495,10 @@ class FileManagementFormView(MultiStepFormView):
 | 
			
		||||
 | 
			
		||||
            # Check if there is a column corresponding to "Supplier SKU"
 | 
			
		||||
            if s_idx >= 0:
 | 
			
		||||
                sku = row['data'][s_idx]
 | 
			
		||||
                sku = row['data'][s_idx]['cell']
 | 
			
		||||
 | 
			
		||||
                # Match for supplier
 | 
			
		||||
                match_supplier = True
 | 
			
		||||
 | 
			
		||||
                try:
 | 
			
		||||
                    # Attempt SupplierPart lookup based on SKU value
 | 
			
		||||
@@ -473,17 +507,27 @@ class FileManagementFormView(MultiStepFormView):
 | 
			
		||||
                    exact_match_part = None
 | 
			
		||||
 | 
			
		||||
            # Check if there is a column corresponding to "Manufacturer MPN"
 | 
			
		||||
            # if m_idx >= 0:
 | 
			
		||||
            #     row['part_mpn'] = row['data'][m_idx]
 | 
			
		||||
            if m_idx >= 0:
 | 
			
		||||
                mpn = row['data'][m_idx]['cell']
 | 
			
		||||
 | 
			
		||||
            #     try:
 | 
			
		||||
            #         # Attempt ManufacturerPart lookup based on MPN value
 | 
			
		||||
            #         exact_match_part = ManufacturerPart.objects.get(MPN=row['part_mpn'])
 | 
			
		||||
            #     except (ValueError, ManufacturerPart.DoesNotExist):
 | 
			
		||||
            #         exact_match_part = None
 | 
			
		||||
                # Match for manufacturer
 | 
			
		||||
                if not match_supplier:
 | 
			
		||||
                    match_manufacturer = True
 | 
			
		||||
 | 
			
		||||
                try:
 | 
			
		||||
                    # Attempt ManufacturerPart lookup based on MPN value
 | 
			
		||||
                    exact_match_part = ManufacturerPart.objects.get(MPN__contains=mpn)
 | 
			
		||||
                except (ValueError, ManufacturerPart.DoesNotExist, ManufacturerPart.MultipleObjectsReturned):
 | 
			
		||||
                    exact_match_part = None
 | 
			
		||||
 | 
			
		||||
            # Check if matching for supplier or manufacturer parts
 | 
			
		||||
            if match_supplier:
 | 
			
		||||
                self.allowed_items = SupplierPart.objects.all()
 | 
			
		||||
            elif match_manufacturer:
 | 
			
		||||
                self.allowed_items = ManufacturerPart.objects.all()
 | 
			
		||||
 | 
			
		||||
            # Supply list of part options for each row, sorted by how closely they match the part name
 | 
			
		||||
            row['item_options'] = item_options
 | 
			
		||||
            row['item_options'] = self.allowed_items
 | 
			
		||||
 | 
			
		||||
            # Unless found, the 'part_match' is blank
 | 
			
		||||
            row['item_match'] = None
 | 
			
		||||
@@ -502,6 +546,16 @@ class FileManagementFormView(MultiStepFormView):
 | 
			
		||||
 | 
			
		||||
        return valid
 | 
			
		||||
 | 
			
		||||
    def checkPartSelection(self, form):
 | 
			
		||||
        """ Check part matching """
 | 
			
		||||
 | 
			
		||||
        # Extract form data
 | 
			
		||||
        self.getFormTableData(form.data)
 | 
			
		||||
 | 
			
		||||
        valid = len(self.extra_context_data.get('missing_columns', [])) == 0 and not self.extra_context_data.get('duplicates', [])
 | 
			
		||||
 | 
			
		||||
        return valid
 | 
			
		||||
 | 
			
		||||
    def validate(self, step, form):
 | 
			
		||||
        """ Validate forms """
 | 
			
		||||
 | 
			
		||||
@@ -519,11 +573,10 @@ class FileManagementFormView(MultiStepFormView):
 | 
			
		||||
                form.add_error(None, 'Fields matching failed')
 | 
			
		||||
 | 
			
		||||
        elif step == 'items':
 | 
			
		||||
            # valid = self.checkPartSelection(form)
 | 
			
		||||
            valid = self.checkPartSelection(form)
 | 
			
		||||
 | 
			
		||||
            # if not valid:
 | 
			
		||||
            #     form.add_error(None, 'Items matching failed')
 | 
			
		||||
            pass
 | 
			
		||||
            if not valid:
 | 
			
		||||
                form.add_error(None, 'Items matching failed')
 | 
			
		||||
 | 
			
		||||
        return valid
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -39,7 +39,7 @@
 | 
			
		||||
            </tr>
 | 
			
		||||
        </thead>
 | 
			
		||||
        <tbody>
 | 
			
		||||
            {% for row in form %}
 | 
			
		||||
            {% for row in rows %}
 | 
			
		||||
            <tr {% if row.errors %} style='background: #ffeaea;'{% endif %} part-name='{{ row.part_name }}' part-description='{{ row.description }}' part-select='#select_part_{{ row.index }}'>
 | 
			
		||||
                <td>
 | 
			
		||||
                    <button class='btn btn-default btn-remove' onClick='removeRowFromBomWizard()' id='del_row_{{ forloop.counter }}' style='display: inline; float: right;' title='{% trans "Remove row" %}'>
 | 
			
		||||
@@ -48,13 +48,18 @@
 | 
			
		||||
                </td>
 | 
			
		||||
                <td></td>
 | 
			
		||||
                <td>
 | 
			
		||||
                {% comment %} {% add row.index 1 %} {% endcomment %}
 | 
			
		||||
                {% add row.index 1 %}
 | 
			
		||||
                </td>
 | 
			
		||||
                <td>
 | 
			
		||||
                    {{ row }}
 | 
			
		||||
                    {% comment %} <button class='btn btn-default btn-create'  onClick='newPartFromBomWizard()' id='new_part_row_{{ row.index }}' title='{% trans "Create new part" %}' type='button'>
 | 
			
		||||
                    <button class='btn btn-default btn-create'  onClick='newPartFromBomWizard()' id='new_part_row_{{ row.index }}' title='{% trans "Create new part" %}' type='button'>
 | 
			
		||||
                        <span row_id='{{ row.index }}' class='fas fa-plus icon-green'/>
 | 
			
		||||
                    </button>
 | 
			
		||||
                    {% for field in form.visible_fields %}
 | 
			
		||||
                        {% if field.name == row.item_select %}
 | 
			
		||||
                            {{ field }}
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                    {% endfor %}
 | 
			
		||||
                    {% comment %} 
 | 
			
		||||
                    <select class='select bomselect' id='select_part_{{ row.index }}' name='part_{{ row.index }}'>
 | 
			
		||||
                        <option value=''>--- {% trans "Select Part" %} ---</option>
 | 
			
		||||
                        {% for part in row.part_options %}
 | 
			
		||||
@@ -69,16 +74,20 @@
 | 
			
		||||
                </td>
 | 
			
		||||
                {% for item in row.data %}
 | 
			
		||||
                <td>
 | 
			
		||||
                    {% comment %} 
 | 
			
		||||
                    {% if item.column.guess == 'Quantity' %}
 | 
			
		||||
                    <input name='quantity_{{ row.index }}' class='numberinput' type='number' min='1' step='any' value='{% decimal row.quantity %}'/>
 | 
			
		||||
                    {% for field in form.visible_fields %}
 | 
			
		||||
                        {% if field.name == row.quantity_select %}
 | 
			
		||||
                            {{ field }}
 | 
			
		||||
                        {% endif %}
 | 
			
		||||
                    {% endfor %}
 | 
			
		||||
                    {% comment %} <input name='quantity_{{ row.index }}' class='numberinput' type='number' min='1' step='any' value='{% decimal row.quantity %}'/> {% endcomment %}
 | 
			
		||||
                    {% if row.errors.quantity %}
 | 
			
		||||
                    <p class='help-inline'>{{ row.errors.quantity }}</p>
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                    {% else %}
 | 
			
		||||
                    {{ item.cell }}
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                    <input type='hidden' name='row_{{ row.index }}_col_{{ forloop.counter0 }}' value='{{ item.cell }}'/> {% endcomment %}
 | 
			
		||||
                    <input type='hidden' name='row_{{ row.index }}_col_{{ forloop.counter0 }}' value='{{ item.cell }}'/>
 | 
			
		||||
                </td>
 | 
			
		||||
                {% endfor %}
 | 
			
		||||
            </tr>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user