Skip to content
View in the app

A better way to browse. Learn more.

DoniaWeB

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.
     

Adding Multiple Suppliers to Products

Adding Multiple Suppliers to Products - Complete Implementation Guide

Overview

This guide will walk you through the complete process of adding multiple suppliers functionality to your Ultimate POS products. This feature allows you to:

  • Assign multiple suppliers to products during creation and editing

  • Filter products by supplier in the products list

  • Bulk update suppliers for multiple products

  • View all suppliers information in product details

  • Use many-to-many relationship between products and suppliers

Adding Supplier to Products - Create Adding Supplier to Products - Index Adding Supplier to Products - Bulk

Download Complete Files

Download All Updated Files

adding-supplier-to-products.zip

Step 1: Database Structure

Migration and Direct SQL

Ultimate POS already has a contacts table that stores suppliers. For multiple suppliers per product, we need to create a pivot table (many-to-many relationship).

Create Migration:

php artisan make:migration create_product_supplier_table

Migration Content:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up()
    {
        Schema::create('product_supplier', function (Blueprint $table) {
            $table->id();
            $table->unsignedInteger('product_id');
            $table->unsignedInteger('supplier_id');
            $table->timestamps();

            // Foreign keys
            $table->foreign('product_id')->references('id')->on('products')->onDelete('cascade');
            $table->foreign('supplier_id')->references('id')->on('contacts')->onDelete('cascade');

            // Prevent duplicate entries
            $table->unique(['product_id', 'supplier_id']);

            // Indexes for better performance
            $table->index('product_id');
            $table->index('supplier_id');
        });
    }

    public function down()
    {
        Schema::dropIfExists('product_supplier');
    }
};

Direct SQL (Alternative):

-- Create pivot table for product-supplier relationship
CREATE TABLE `product_supplier` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `product_id` INT UNSIGNED NOT NULL,
  `supplier_id` INT UNSIGNED NOT NULL,
  `created_at` TIMESTAMP NULL,
  `updated_at` TIMESTAMP NULL,

  -- Foreign keys
  CONSTRAINT `fk_product_supplier_product_id`
    FOREIGN KEY (`product_id`) REFERENCES `products`(`id`) ON DELETE CASCADE,
  CONSTRAINT `fk_product_supplier_supplier_id`
    FOREIGN KEY (`supplier_id`) REFERENCES `contacts`(`id`) ON DELETE CASCADE,

  -- Unique constraint to prevent duplicates
  UNIQUE KEY `unique_product_supplier` (`product_id`, `supplier_id`),

  -- Indexes for performance
  INDEX `idx_product_supplier_product_id` (`product_id`),
  INDEX `idx_product_supplier_supplier_id` (`supplier_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Run Migration:

php artisan migrate

Note: This creates a many-to-many relationship allowing each product to have multiple suppliers and each supplier to supply multiple products.

Step 2: Model Updates

Update Product Model

File: app/Product.php

Add relationship method for many-to-many relationship:

/**
 * Get the suppliers for the product (many-to-many).
 */
public function suppliers()
{
    return $this->belongsToMany(
        \App\Contact::class,
        'product_supplier',
        'product_id',
        'supplier_id'
    )->withTimestamps();
}

File: app/Contact.php

Add relationship method for many-to-many relationship:

/**
 * Get products supplied by this supplier (many-to-many)
 */
public function products()
{
    return $this->belongsToMany(
        \App\Product::class,
        'product_supplier',
        'supplier_id',
        'product_id'
    )->withTimestamps();
}

/**
 * Get products supplied by this supplier with stock enabled
 */
public function stockProducts()
{
    return $this->belongsToMany(
        \App\Product::class,
        'product_supplier',
        'supplier_id',
        'product_id'
    )->where('products.enable_stock', 1)
     ->withTimestamps();
}

Important Notes:

  • The belongsToMany relationship requires the pivot table name (product_supplier)

  • Use sync() method to attach/detach suppliers in controllers

  • The withTimestamps() method ensures the pivot table timestamps are maintained

Step 3: Controller Updates

ProductController Changes

File: app/Http/Controllers/ProductController.php

3.1 Update index() method

Add suppliers to eager loading (around line 77):

$query = Product::with(['media', 'suppliers'])

Add supplier filter to query (around line 162):

$supplier_id = request()->get('supplier_id', null);
if (!empty($supplier_id)) {
    $products->whereHas('suppliers', function ($query) use ($supplier_id) {
        $query->where('contacts.id', $supplier_id);
    });
}

Add suppliers column to DataTables (after selling_price column):

->addColumn('suppliers', function ($row) {
    $suppliers = $row->suppliers->pluck('name')->toArray();
    return implode(', ', $suppliers);
})

Add suppliers dropdown for view (around line 334):

$suppliers = \App\Contact::suppliersDropdown($business_id);

Update compact statement to include suppliers:

return view('product.index')
    ->with(compact(
        'rack_enabled',
        'categories',
        'brands',
        'units',
        'taxes',
        'business_locations',
        'show_manufacturing_data',
        'pos_module_data',
        'is_woocommerce',
        'is_admin',
        'suppliers'
    ));

3.2 Update create() method

Add suppliers dropdown (around line 150):

$suppliers = \App\Contact::suppliersDropdown($business_id);

Update compact statement:

return view('product.create')
    ->with(compact('categories', 'brands', 'units', 'taxes', 'barcode_types', 
    'default_profit_percent', 'tax_attributes', 'barcode_default', 'business_locations', 
    'duplicate_product', 'sub_categories', 'rack_details', 'selling_price_group_count', 
    'module_form_parts', 'product_types', 'common_settings', 'warranties', 
    'pos_module_data', 'suppliers'));

3.3 Update store() method

Add supplier sync after product creation (after product locations sync, around line 645):

// Sync product suppliers
$supplier_ids = $request->input('supplier_ids', []);
if (!empty($supplier_ids)) {
    $product->suppliers()->sync($supplier_ids);
}

3.4 Update edit() method

Add suppliers dropdown (around line 350):

$suppliers = \App\Contact::suppliersDropdown($business_id);

Update compact statement:

return view('product.edit')
    ->with(compact('categories', 'brands', 'units', 'sub_units', 'taxes', 'tax_attributes', 
    'barcode_types', 'product', 'sub_categories', 'default_profit_percent', 'business_locations', 
    'rack_details', 'selling_price_group_count', 'module_form_parts', 'product_types', 
    'common_settings', 'warranties', 'pos_module_data', 'alert_quantity', 'suppliers'));

3.5 Update update() method

Add supplier sync after product update (after product locations sync, around line 820):

// Sync product suppliers
$supplier_ids = $request->input('supplier_ids', []);
$product->suppliers()->sync($supplier_ids);

3.6 Update view() method

Add suppliers to with array (around line 920):

$product = Product::where('business_id', $business_id)
    ->with(['brand', 'unit', 'category', 'sub_category', 'product_tax', 'variations', 'variations.product_variation', 'variations.group_prices', 'variations.media', 'product_locations', 'warranty', 'media', 'suppliers'])
    ->findOrFail($id);

3.7 Add bulk supplier update method

public function bulkUpdateSupplier(Request $request)
{
    if (!auth()->user()->can('product.update')) {
        abort(403, 'Unauthorized action.');
    }

    try {
        $selected_products = $request->input('selected_products');
        $supplier_ids = $request->input('supplier_ids', []);
        $business_id = $request->session()->get('user.business_id');

        if (empty($selected_products)) {
            $output = [
                'success' => 0,
                'msg' => __('lang_v1.no_products_selected')
            ];
            return $output;
        }

        $product_ids = explode(',', $selected_products);

        DB::beginTransaction();

        // Get products and sync suppliers for each
        $products = Product::where('business_id', $business_id)
            ->whereIn('id', $product_ids)
            ->get();

        foreach ($products as $product) {
            $product->suppliers()->sync($supplier_ids);
        }

        DB::commit();

        $output = [
            'success' => 1,
            'msg' => __('lang_v1.supplier_updated_success')
        ];
    } catch (\Exception $e) {
        DB::rollBack();
        \Log::emergency("File:" . $e->getFile() . "Line:" . $e->getLine() . "Message:" . $e->getMessage());

        $output = [
            'success' => 0,
            'msg' => __('messages.something_went_wrong')
        ];
    }

    return $output;
}

3.8 Update other methods

quickAdd() method (around line 1500):

Add suppliers dropdown:

$suppliers = \App\Contact::suppliersDropdown($business_id, false);

Update the compact statement to include suppliers:

return view('product.partials.quick_add_product')
    ->with(compact('categories', 'brands', 'units', 'taxes', 'barcode_types', 'default_profit_percent', 'tax_attributes', 'product_name', 'locations', 'product_for', 'enable_expiry', 'enable_lot', 'module_form_parts', 'business_locations', 'common_settings', 'warranties', 'suppliers'));

saveQuickProduct() method:

The supplier_ids handling is done via sync relationship, NOT through form_fields. Add this code after syncing product_locations (around line 1610):

//Add product suppliers
$supplier_ids = $request->input('supplier_ids');
if (!empty($supplier_ids)) {
    $product->suppliers()->sync($supplier_ids);
}

bulkEdit() and bulkUpdate() methods:

// Add supplier handling as shown in previous implementation guide

Step 4: Routes

File: routes/web.php

Add the bulk supplier update route:

Route::post('/products/bulk-update-supplier', [ProductController::class, 'bulkUpdateSupplier'])
    ->name('products.bulk-update-supplier');

Step 5: View Updates

5.1 Product Index Page

File: resources/views/product/index.blade.php

Add supplier filter (after brand filter around line 90):

<div class="col-md-3">
    <div class="form-group">
        {!! Form::label('supplier_id', __('contact.supplier') . ':') !!}
        {!! Form::select('supplier_id', $suppliers ?? [], null, [
            'class' => 'form-control select2',
            'style' => 'width:100%',
            'id' => 'product_list_filter_supplier_id',
            'placeholder' => __('lang_v1.all'),
        ]) !!}
    </div>
</div>

Update JavaScript DataTable configuration:

Add to ajax data function:

d.supplier_id = $('#product_list_filter_supplier_id').val();

Add to columns array (after brand column):

{
    data: 'suppliers',
    name: 'suppliers',
    orderable: false,
    searchable: false
},

Update change event listener:

$(document).on('change',
    '#product_list_filter_type, #product_list_filter_category_id, #product_list_filter_brand_id, #product_list_filter_supplier_id, #product_list_filter_unit_id, #product_list_filter_tax_id, #location_id, #active_state, #repair_model_id',
    function() {
        // existing code
    });

Add bulk supplier modal:

<!-- Bulk Supplier Update Modal -->
<div class="modal fade" id="bulk_supplier_modal" tabindex="-1" role="dialog">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal">
                    <span>&times;</span>
                </button>
                <h4 class="modal-title">@lang('lang_v1.bulk_update_supplier')</h4>
            </div>
            <form id="bulk_supplier_form" method="POST" action="{{ route('products.bulk-update-supplier') }}">
                @csrf
                <div class="modal-body">
                    <div class="form-group">
                        <label for="bulk_supplier_id">@lang('contact.supplier'):</label>
                        <select name="supplier_id" id="bulk_supplier_id" class="form-control select2" style="width: 100%;">
                            <option value="">@lang('lang_v1.none')</option>
                            @if(isset($suppliers))
                                @foreach($suppliers as $id => $name)
                                    @if($id != '')
                                        <option value="{{ $id }}">{{ $name }}</option>
                                    @endif
                                @endforeach
                            @endif
                        </select>
                    </div>
                    <input type="hidden" name="selected_products" id="bulk_selected_products">
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-default" data-dismiss="modal">@lang('messages.close')</button>
                    <button type="submit" class="btn btn-primary">@lang('messages.update')</button>
                </div>
            </form>
        </div>
    </div>
</div>

Add bulk supplier JavaScript:

// Bulk supplier update functionality
$(document).on('click', '.bulk-update-supplier', function(e) {
    e.preventDefault();
    var selected_rows = getSelectedRows();

    if (selected_rows.length > 0) {
        $('#bulk_selected_products').val(selected_rows);
        $('#bulk_supplier_modal').modal('show');
        
        $('#bulk_supplier_id').select2({
            dropdownParent: $('#bulk_supplier_modal')
        });
    } else {
        swal('@lang("lang_v1.no_row_selected")');
    }
});

$(document).on('submit', '#bulk_supplier_form', function(e) {
    e.preventDefault();
    var form = $(this);
    var data = form.serialize();

    $.ajax({
        method: 'POST',
        url: form.attr('action'),
        dataType: 'json',
        data: data,
        beforeSend: function(xhr) {
            form.find('button[type="submit"]').attr('disabled', true);
        },
        success: function(result) {
            if (result.success == 1) {
                $('#bulk_supplier_modal').modal('hide');
                toastr.success(result.msg);
                product_table.ajax.reload();
            } else {
                toastr.error(result.msg);
            }
            form.find('button[type="submit"]').attr('disabled', false);
        },
        error: function(xhr) {
            toastr.error('@lang("messages.something_went_wrong")');
            form.find('button[type="submit"]').attr('disabled', false);
        }
    });
});

5.2 Product Table

File: resources/views/product/partials/product_list.blade.php

Add supplier column header (after brand):

<th>@lang('lang_v1.suppliers')</th>

Add bulk supplier button (in tfoot after existing buttons):

&nbsp;
<button type="button" class="tw-dw-btn tw-dw-btn-xs tw-dw-btn-outline tw-dw-btn-info bulk-update-supplier">
    <i class="fa fa-users"></i> @lang('lang_v1.bulk_update_supplier')
</button>

5.3 Product Forms

Create Form (resources/views/product/create.blade.php):

Add after brand field (around line 87):

<div class="col-sm-4">
    <div class="form-group">
        {!! Form::label('supplier_ids', __('lang_v1.suppliers') . ':') !!}
        <div class="input-group">
            {!! Form::select('supplier_ids[]', $suppliers, !empty($duplicate_product) ? $duplicate_product->suppliers->pluck('id')->toArray() : null, ['class' => 'form-control select2', 'multiple', 'id' => 'supplier_ids']); !!}
            <span class="input-group-btn">
                <button type="button" @if(!auth()->user()->can('supplier.create')) disabled @endif class="btn btn-default bg-white btn-flat quick_add_supplier_btn" title="@lang('contact.add_supplier')"><i class="fa fa-plus-circle text-primary fa-lg"></i></button>
            </span>
        </div>
    </div>
</div>

Edit Form (resources/views/product/edit.blade.php):

Add after brand field (around line 98):

<div class="col-sm-4">
    <div class="form-group">
        {!! Form::label('supplier_ids', __('lang_v1.suppliers') . ':') !!}
        <div class="input-group">
            {!! Form::select('supplier_ids[]', $suppliers, $product->suppliers->pluck('id')->toArray(), ['class' => 'form-control select2', 'multiple', 'id' => 'supplier_ids']); !!}
            <span class="input-group-btn">
                <button type="button" @if(!auth()->user()->can('supplier.create')) disabled @endif class="btn btn-default bg-white btn-flat quick_add_supplier_btn" title="@lang('contact.add_supplier')"><i class="fa fa-plus-circle text-primary fa-lg"></i></button>
            </span>
        </div>
    </div>
</div>

Note: The edit form pre-populates with the product's current suppliers using $product->suppliers->pluck('id')->toArray()

5.4 Quick Add Supplier Modal (Simplified Form)

This section implements a simplified quick add supplier modal similar to the purchase page, with only essential fields: Supplier Name (or Business Name) and Mobile Number, plus Individual/Business toggle.

File: resources/views/product/partials/quick_add_supplier_modal.blade.php (Create this new file)

<!-- Quick Add Supplier Modal for Product Pages -->
<div class="modal fade" id="quick_add_supplier_modal" tabindex="-1" role="dialog" aria-labelledby="quickAddSupplierModalLabel">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            {!! Form::open(['url' => action([\App\Http\Controllers\ContactController::class, 'store']), 'method' => 'post', 'id' => 'quick_add_supplier_form']) !!}

            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
                <h4 class="modal-title" id="quickAddSupplierModalLabel">@lang('contact.add_supplier')</h4>
            </div>

            <div class="modal-body">
                {!! Form::hidden('type', 'supplier') !!}

                <div class="row">
                    <!-- Individual / Business Toggle -->
                    <div class="col-md-12" style="margin-bottom: 15px;">
                        <label class="radio-inline">
                            <input type="radio" name="supplier_type_radio" class="supplier_type_radio" value="individual" checked>
                            @lang('lang_v1.individual')
                        </label>
                        &nbsp;&nbsp;&nbsp;
                        <label class="radio-inline">
                            <input type="radio" name="supplier_type_radio" class="supplier_type_radio" value="business">
                            @lang('business.business')
                        </label>
                    </div>

                    <div class="clearfix"></div>

                    <!-- Individual Fields -->
                    <div class="col-md-6 supplier_individual_fields">
                        <div class="form-group">
                            {!! Form::label('first_name', __('business.first_name') . ':*') !!}
                            <div class="input-group">
                                <span class="input-group-addon">
                                    <i class="fa fa-user"></i>
                                </span>
                                {!! Form::text('first_name', null, ['class' => 'form-control', 'placeholder' => __('business.first_name'), 'id' => 'supplier_first_name']); !!}
                            </div>
                        </div>
                    </div>
                    <div class="col-md-6 supplier_individual_fields">
                        <div class="form-group">
                            {!! Form::label('last_name', __('business.last_name') . ':') !!}
                            <div class="input-group">
                                <span class="input-group-addon">
                                    <i class="fa fa-user"></i>
                                </span>
                                {!! Form::text('last_name', null, ['class' => 'form-control', 'placeholder' => __('business.last_name'), 'id' => 'supplier_last_name']); !!}
                            </div>
                        </div>
                    </div>

                    <!-- Business Fields (Hidden by default) -->
                    <div class="col-md-12 supplier_business_fields" style="display: none;">
                        <div class="form-group">
                            {!! Form::label('supplier_business_name', __('business.business_name') . ':*') !!}
                            <div class="input-group">
                                <span class="input-group-addon">
                                    <i class="fa fa-briefcase"></i>
                                </span>
                                {!! Form::text('supplier_business_name', null, ['class' => 'form-control', 'placeholder' => __('business.business_name'), 'id' => 'supplier_business_name_input']); !!}
                            </div>
                        </div>
                    </div>

                    <!-- Mobile Number (Always visible) -->
                    <div class="col-md-6">
                        <div class="form-group">
                            {!! Form::label('mobile', __('contact.mobile') . ':*') !!}
                            <div class="input-group">
                                <span class="input-group-addon">
                                    <i class="fa fa-mobile"></i>
                                </span>
                                {!! Form::text('mobile', null, ['class' => 'form-control', 'required', 'placeholder' => __('contact.mobile'), 'id' => 'supplier_mobile']); !!}
                            </div>
                        </div>
                    </div>

                    <!-- Email (Optional) -->
                    <div class="col-md-6">
                        <div class="form-group">
                            {!! Form::label('email', __('business.email') . ':') !!}
                            <div class="input-group">
                                <span class="input-group-addon">
                                    <i class="fa fa-envelope"></i>
                                </span>
                                {!! Form::email('email', null, ['class' => 'form-control', 'placeholder' => __('business.email'), 'id' => 'supplier_email']); !!}
                            </div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="modal-footer">
                <button type="button" class="tw-dw-btn tw-dw-btn-neutral tw-text-white" data-dismiss="modal">@lang('messages.close')</button>
                <button type="submit" class="tw-dw-btn tw-dw-btn-primary tw-text-white">@lang('messages.save')</button>
            </div>

            {!! Form::close() !!}
        </div>
    </div>
</div>

Include the modal in product create/edit pages:

Add at the bottom of resources/views/product/create.blade.php and resources/views/product/edit.blade.php:

@include('product.partials.quick_add_supplier_modal')

5.5 JavaScript for Quick Add Supplier

Add the following JavaScript to handle the quick add supplier modal. You can add this to public/js/product.js or include it in the product pages.

Add to public/js/product.js or inline in product views:

// Quick Add Supplier Modal - Toggle Individual/Business fields
$(document).on('change', '.supplier_type_radio', function() {
    var selectedType = $(this).val();

    if (selectedType === 'individual') {
        $('.supplier_individual_fields').show();
        $('.supplier_business_fields').hide();
        // Update required attributes
        $('#supplier_first_name').prop('required', true);
        $('#supplier_business_name_input').prop('required', false);
    } else if (selectedType === 'business') {
        $('.supplier_individual_fields').hide();
        $('.supplier_business_fields').show();
        // Update required attributes
        $('#supplier_first_name').prop('required', false);
        $('#supplier_business_name_input').prop('required', true);
    }
});

// Open Quick Add Supplier Modal
$(document).on('click', '.quick_add_supplier_btn', function(e) {
    e.preventDefault();
    // Reset form
    $('#quick_add_supplier_form')[0].reset();
    // Reset to individual by default
    $('input[name="supplier_type_radio"][value="individual"]').prop('checked', true).trigger('change');
    // Show modal
    $('#quick_add_supplier_modal').modal('show');
});

// Handle Quick Add Supplier Form Submission
$(document).on('submit', '#quick_add_supplier_form', function(e) {
    e.preventDefault();

    var form = $(this);
    var submitBtn = form.find('button[type="submit"]');
    var formData = form.serialize();

    $.ajax({
        method: 'POST',
        url: form.attr('action'),
        data: formData,
        dataType: 'json',
        beforeSend: function() {
            submitBtn.prop('disabled', true);
            submitBtn.html('<i class="fa fa-spinner fa-spin"></i> ' + LANG.please_wait);
        },
        success: function(result) {
            if (result.success === true) {
                // Close modal
                $('#quick_add_supplier_modal').modal('hide');

                // Show success message
                toastr.success(result.msg);

                // Add the new supplier to the supplier_ids select2 dropdown
                var newOption = new Option(result.data.name, result.data.id, true, true);
                $('#supplier_ids').append(newOption).trigger('change');

                // Reset form
                form[0].reset();
            } else {
                toastr.error(result.msg);
            }
        },
        error: function(xhr) {
            var errorMsg = xhr.responseJSON && xhr.responseJSON.message
                ? xhr.responseJSON.message
                : LANG.something_went_wrong;
            toastr.error(errorMsg);
        },
        complete: function() {
            submitBtn.prop('disabled', false);
            submitBtn.html(LANG.save);
        }
    });
});

// Initialize modal on page load
$(document).ready(function() {
    // Ensure individual fields are shown by default
    $('.supplier_individual_fields').show();
    $('.supplier_business_fields').hide();
});

5.6 Controller Update for AJAX Response

Update the ContactController@store method to return JSON response when called via AJAX:

File: app/Http/Controllers/ContactController.php

Find the store() method and update the success response to handle AJAX requests:

// At the end of the store() method, before the redirect, add:
if ($request->ajax()) {
    // Get the display name for the supplier
    $displayName = $contact->supplier_business_name
        ? $contact->supplier_business_name
        : trim($contact->prefix . ' ' . $contact->first_name . ' ' . $contact->middle_name . ' ' . $contact->last_name);

    return response()->json([
        'success' => true,
        'msg' => __('contact.added_success'),
        'data' => [
            'id' => $contact->id,
            'name' => $displayName,
            'mobile' => $contact->mobile,
            'type' => $contact->type
        ]
    ]);
}

Full example of modified store() method ending:

// ... existing store logic ...

DB::commit();

$output = [
    'success' => true,
    'data' => $contact,
    'msg' => __('contact.added_success')
];

// Handle AJAX requests (for quick add modals)
if ($request->ajax()) {
    $displayName = $contact->supplier_business_name
        ? $contact->supplier_business_name
        : trim($contact->prefix . ' ' . $contact->first_name . ' ' . $contact->middle_name . ' ' . $contact->last_name);

    return response()->json([
        'success' => true,
        'msg' => __('contact.added_success'),
        'data' => [
            'id' => $contact->id,
            'name' => $displayName,
            'mobile' => $contact->mobile,
            'type' => $contact->type
        ]
    ]);
}

// Existing redirect logic for non-AJAX requests
return redirect('contacts')->with('status', $output);

5.7 Product View Modal

File: resources/views/product/view-modal.blade.php

Add supplier information after brand:

<b>@lang('contact.supplier'): </b>
@if($product->suppliers->isNotEmpty())
    {{ $product->suppliers->pluck('name')->implode(', ') }}
@else
    --
@endif
<br>

Note: Since a product can have multiple suppliers, we display them as a comma-separated list.

5.8 Quick Add Product Form

File: resources/views/product/partials/quick_add_product.blade.php

Add after brand field (around line 57):

<div class="col-sm-4">
    <div class="form-group">
        {!! Form::label('supplier_ids', __('contact.supplier') . ':') !!}
        <div class="input-group">
            {!! Form::select('supplier_ids[]', $suppliers, null, ['class' => 'form-control select2', 'multiple', 'id' => 'quick_product_supplier_ids']); !!}
            <span class="input-group-btn">
                <button type="button" @if(!auth()->user()->can('supplier.create')) disabled @endif class="btn btn-default bg-white btn-flat quick_add_supplier_btn" title="@lang('contact.add_supplier')"><i class="fa fa-plus-circle text-primary fa-lg"></i></button>
            </span>
        </div>
    </div>
</div>

Note: The quick add supplier modal will also work in this form since the JavaScript is event-delegated.

Important Notes:

  • Use supplier_ids[] (array) for multiple supplier selection

  • Add 'multiple' => true to enable multi-select

  • The quick add supplier button uses the same simplified modal

  • The field name must match what saveQuickProduct() expects

Step 6: Language Files

File: resources/lang/en/lang_v1.php

Add these translations:

'bulk_update_supplier' => 'Bulk Update Supplier',
'supplier_updated_success' => 'Supplier updated successfully',
'no_products_selected' => 'No products selected',

Step 7: Testing

7.1 Basic Functionality

  1. Create Product: Test creating a product with multiple supplier selection

  2. Edit Product: Test editing existing products and changing/adding/removing suppliers

  3. View Product: Verify all suppliers appear in product details modal (comma-separated)

  4. Filter Products: Test filtering products by supplier in the index page

7.2 Bulk Operations

  1. Select Multiple Products: Use checkboxes to select multiple products

  2. Bulk Update Supplier: Click bulk supplier button and assign multiple suppliers

  3. Verify Changes: Check that all selected products have the new suppliers (replaces existing)

7.3 Edge Cases

  1. No Supplier Selected: Test with empty/null supplier values (should work fine)

  2. Invalid Supplier: Test with non-existent supplier IDs

  3. Permission Testing: Test with different user permissions

  4. Large Datasets: Test with many products and many suppliers selected

  5. Duplicate Prevention: Try to assign the same supplier twice (should be prevented by unique constraint)

Step 8: Optional Enhancements

8.1 Supplier Statistics

Add supplier-based reports showing:

  • Products per supplier

  • Stock levels by supplier

  • Purchase history by supplier

8.2 Advanced Filtering

Add more complex filtering options:

  • Products without suppliers

  • Supplier-based stock alerts

  • Multi-supplier selection

8.3 Import/Export

Update product import/export to include supplier information:

  • CSV import with supplier names

  • Excel export with supplier details

Conclusion

You have successfully implemented multiple suppliers functionality for products in Ultimate POS. This feature provides:

  •  Multiple supplier assignment during product creation/editing

  •  Many-to-many relationship between products and suppliers

  • Supplier filtering in product listings

  •  Bulk supplier updates for multiple products (with multiple supplier selection)

  •  All suppliers information displayed in product details

  • Integration with existing supplier management

  •  Pivot table (product_supplier) for efficient relationship management

  • Duplicate prevention via unique constraints

  •  Quick Add Supplier modal with simplified form (Individual/Business toggle, Name, Mobile)

The implementation follows Ultimate POS conventions and maintains compatibility with existing features while supporting the flexibility of multiple suppliers per product.

Key Differences from Single Supplier Implementation

  • Uses belongsToMany instead of belongsTo/hasMany

  • Requires pivot table (product_supplier)

  • Form fields use supplier_ids[] instead of supplier_id

  • Controllers use sync() method for relationship management

  • Views display comma-separated supplier names

Quick Add Supplier Features

The simplified quick add supplier modal (similar to /purchases/create) provides:

  • Individual/Business toggle - Switch between individual supplier (First Name, Last Name) and business supplier (Business Name)

  • Minimum required fields - Only Supplier Name and Mobile Number are required

  • Optional Email field - For additional contact information

  • AJAX submission - Seamless addition without page reload

  • Auto-select - Newly added supplier is automatically selected in the dropdown

0 Comments

Recommended Comments

There are no comments to display.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Add a comment...

Account

Navigation

Search

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.