fix: конфигуратор зависал на «Загрузка...», infinite retry при sync, UpsertByUUID
1. JS-конфигуратор: при загрузке сохранённой конфигурации item.category всегда undefined (в config.items хранится только lot_name/quantity/unit_price). Добавлено обогащение cart из allComponents после загрузки, все сравнения категорий переведены на ciStr() вместо .toUpperCase(), исправлены все 4 точки построения ASSIGNED_CATEGORIES — устраняет TypeError и таб «Other» показывал компоненты с известными категориями. 2. RepairPendingChanges: repair-функции теперь возвращают (bool, error); attempts/last_error сбрасываются только при modified=true — устраняет бесконечный retry когда ошибка на стороне сервера, а не локальных данных. 3. UpsertByUUID: сброс project.ID=0 перед INSERT … ON DUPLICATE KEY UPDATE, чтобы конфликт шёл по уникальному uuid, а не по PK чужой строки — устраняет «record not found» при разрешении изменений проекта. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1720,12 +1720,13 @@ func (l *LocalDB) RepairPendingChanges() (int, []string, error) {
|
|||||||
var remainingErrors []string
|
var remainingErrors []string
|
||||||
|
|
||||||
for _, change := range erroredChanges {
|
for _, change := range erroredChanges {
|
||||||
|
var modified bool
|
||||||
var repairErr error
|
var repairErr error
|
||||||
switch change.EntityType {
|
switch change.EntityType {
|
||||||
case "project":
|
case "project":
|
||||||
repairErr = l.repairProjectChange(&change)
|
modified, repairErr = l.repairProjectChange(&change)
|
||||||
case "configuration":
|
case "configuration":
|
||||||
repairErr = l.repairConfigurationChange(&change)
|
modified, repairErr = l.repairConfigurationChange(&change)
|
||||||
default:
|
default:
|
||||||
repairErr = fmt.Errorf("unknown entity type: %s", change.EntityType)
|
repairErr = fmt.Errorf("unknown entity type: %s", change.EntityType)
|
||||||
}
|
}
|
||||||
@@ -1736,7 +1737,13 @@ func (l *LocalDB) RepairPendingChanges() (int, []string, error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear error and reset attempts
|
// Only reset attempts when the repair actually changed local data.
|
||||||
|
// If nothing was modified, the error is server-side; leaving attempts
|
||||||
|
// intact lets maxPendingChangeAttempts eventually abandon the change.
|
||||||
|
if !modified {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if err := l.db.Model(&PendingChange{}).Where("id = ?", change.ID).Updates(map[string]interface{}{
|
if err := l.db.Model(&PendingChange{}).Where("id = ?", change.ID).Updates(map[string]interface{}{
|
||||||
"last_error": "",
|
"last_error": "",
|
||||||
"attempts": 0,
|
"attempts": 0,
|
||||||
@@ -1752,12 +1759,13 @@ func (l *LocalDB) RepairPendingChanges() (int, []string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// repairProjectChange validates and fixes project data.
|
// repairProjectChange validates and fixes project data.
|
||||||
|
// Returns (modified, err): modified=true only when local data was actually changed.
|
||||||
// Note: This only validates local data. Server-side conflicts (like duplicate code+variant)
|
// Note: This only validates local data. Server-side conflicts (like duplicate code+variant)
|
||||||
// are handled by sync service layer with deduplication logic.
|
// are handled by sync service layer with deduplication logic.
|
||||||
func (l *LocalDB) repairProjectChange(change *PendingChange) error {
|
func (l *LocalDB) repairProjectChange(change *PendingChange) (bool, error) {
|
||||||
project, err := l.GetProjectByUUID(change.EntityUUID)
|
project, err := l.GetProjectByUUID(change.EntityUUID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("project not found locally: %w", err)
|
return false, fmt.Errorf("project not found locally: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
modified := false
|
modified := false
|
||||||
@@ -1783,7 +1791,7 @@ func (l *LocalDB) repairProjectChange(change *PendingChange) error {
|
|||||||
if strings.TrimSpace(project.OwnerUsername) == "" {
|
if strings.TrimSpace(project.OwnerUsername) == "" {
|
||||||
project.OwnerUsername = l.GetDBUser()
|
project.OwnerUsername = l.GetDBUser()
|
||||||
if project.OwnerUsername == "" {
|
if project.OwnerUsername == "" {
|
||||||
return fmt.Errorf("cannot determine owner username")
|
return false, fmt.Errorf("cannot determine owner username")
|
||||||
}
|
}
|
||||||
modified = true
|
modified = true
|
||||||
}
|
}
|
||||||
@@ -1804,18 +1812,19 @@ func (l *LocalDB) repairProjectChange(change *PendingChange) error {
|
|||||||
|
|
||||||
if modified {
|
if modified {
|
||||||
if err := l.SaveProject(project); err != nil {
|
if err := l.SaveProject(project); err != nil {
|
||||||
return fmt.Errorf("saving repaired project: %w", err)
|
return false, fmt.Errorf("saving repaired project: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return modified, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// repairConfigurationChange validates and fixes configuration data
|
// repairConfigurationChange validates and fixes configuration data.
|
||||||
func (l *LocalDB) repairConfigurationChange(change *PendingChange) error {
|
// Returns (modified, err): modified=true only when local data was actually changed.
|
||||||
|
func (l *LocalDB) repairConfigurationChange(change *PendingChange) (bool, error) {
|
||||||
config, err := l.GetConfigurationByUUID(change.EntityUUID)
|
config, err := l.GetConfigurationByUUID(change.EntityUUID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("configuration not found locally: %w", err)
|
return false, fmt.Errorf("configuration not found locally: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
modified := false
|
modified := false
|
||||||
@@ -1827,7 +1836,7 @@ func (l *LocalDB) repairConfigurationChange(change *PendingChange) error {
|
|||||||
// Project doesn't exist locally - use default system project
|
// Project doesn't exist locally - use default system project
|
||||||
systemProject, sysErr := l.EnsureDefaultProject(config.OriginalUsername)
|
systemProject, sysErr := l.EnsureDefaultProject(config.OriginalUsername)
|
||||||
if sysErr != nil {
|
if sysErr != nil {
|
||||||
return fmt.Errorf("getting system project: %w", sysErr)
|
return false, fmt.Errorf("getting system project: %w", sysErr)
|
||||||
}
|
}
|
||||||
config.ProjectUUID = &systemProject.UUID
|
config.ProjectUUID = &systemProject.UUID
|
||||||
modified = true
|
modified = true
|
||||||
@@ -1836,11 +1845,11 @@ func (l *LocalDB) repairConfigurationChange(change *PendingChange) error {
|
|||||||
|
|
||||||
if modified {
|
if modified {
|
||||||
if err := l.SaveConfiguration(config); err != nil {
|
if err := l.SaveConfiguration(config); err != nil {
|
||||||
return fmt.Errorf("saving repaired configuration: %w", err)
|
return false, fmt.Errorf("saving repaired configuration: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return modified, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSyncGuardState returns the latest readiness guard state.
|
// GetSyncGuardState returns the latest readiness guard state.
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ func (r *ProjectRepository) Update(project *models.Project) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *ProjectRepository) UpsertByUUID(project *models.Project) error {
|
func (r *ProjectRepository) UpsertByUUID(project *models.Project) error {
|
||||||
|
// Clear the client-side primary key so the upsert is driven purely by the
|
||||||
|
// uuid unique constraint. Passing a non-zero ID can trigger ON DUPLICATE KEY
|
||||||
|
// on the primary key of an unrelated row, leaving uuid unchanged and causing
|
||||||
|
// the follow-up SELECT to return ErrRecordNotFound.
|
||||||
|
project.ID = 0
|
||||||
if err := r.db.Clauses(clause.OnConflict{
|
if err := r.db.Clauses(clause.OnConflict{
|
||||||
Columns: []clause.Column{{Name: "uuid"}},
|
Columns: []clause.Column{{Name: "uuid"}},
|
||||||
DoUpdates: clause.AssignmentColumns([]string{
|
DoUpdates: clause.AssignmentColumns([]string{
|
||||||
|
|||||||
@@ -417,7 +417,7 @@ let TAB_CONFIG = {
|
|||||||
|
|
||||||
let ASSIGNED_CATEGORIES = Object.values(TAB_CONFIG)
|
let ASSIGNED_CATEGORIES = Object.values(TAB_CONFIG)
|
||||||
.flatMap(t => t.categories)
|
.flatMap(t => t.categories)
|
||||||
.map(c => c.toUpperCase());
|
.map(c => ciStr(c));
|
||||||
|
|
||||||
// State
|
// State
|
||||||
let configUUID = '{{.ConfigUUID}}';
|
let configUUID = '{{.ConfigUUID}}';
|
||||||
@@ -760,16 +760,16 @@ async function loadCategoriesFromAPI() {
|
|||||||
// Build category order map
|
// Build category order map
|
||||||
categoryOrderMap = {};
|
categoryOrderMap = {};
|
||||||
cats.forEach(cat => {
|
cats.forEach(cat => {
|
||||||
categoryOrderMap[cat.code.toUpperCase()] = cat.display_order;
|
categoryOrderMap[ciStr(cat.code)] = cat.display_order;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Build list of unassigned categories
|
// Build list of unassigned categories
|
||||||
const knownCodes = Object.values(TAB_CONFIG)
|
const knownCodes = Object.values(TAB_CONFIG)
|
||||||
.flatMap(t => t.categories)
|
.flatMap(t => t.categories)
|
||||||
.map(c => c.toUpperCase());
|
.map(c => ciStr(c));
|
||||||
|
|
||||||
const unassignedCategories = cats
|
const unassignedCategories = cats
|
||||||
.filter(cat => !knownCodes.includes(cat.code.toUpperCase()))
|
.filter(cat => !knownCodes.includes(ciStr(cat.code)))
|
||||||
.sort((a, b) => a.display_order - b.display_order)
|
.sort((a, b) => a.display_order - b.display_order)
|
||||||
.map(cat => cat.code);
|
.map(cat => cat.code);
|
||||||
|
|
||||||
@@ -779,7 +779,7 @@ async function loadCategoriesFromAPI() {
|
|||||||
// Rebuild ASSIGNED_CATEGORIES
|
// Rebuild ASSIGNED_CATEGORIES
|
||||||
ASSIGNED_CATEGORIES = Object.values(TAB_CONFIG)
|
ASSIGNED_CATEGORIES = Object.values(TAB_CONFIG)
|
||||||
.flatMap(t => t.categories)
|
.flatMap(t => t.categories)
|
||||||
.map(c => c.toUpperCase());
|
.map(c => ciStr(c));
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.error('Failed to load categories, using defaults', e);
|
console.error('Failed to load categories, using defaults', e);
|
||||||
// Will use default configuration if API fails
|
// Will use default configuration if API fails
|
||||||
@@ -824,7 +824,7 @@ function applyServerSettings(settings) {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
TAB_CONFIG.other = otherTab || { categories: [], singleSelect: false, label: 'Other' };
|
TAB_CONFIG.other = otherTab || { categories: [], singleSelect: false, label: 'Other' };
|
||||||
ASSIGNED_CATEGORIES = Object.values(TAB_CONFIG).flatMap(t => t.categories).map(c => c.toUpperCase());
|
ASSIGNED_CATEGORIES = Object.values(TAB_CONFIG).flatMap(t => t.categories).map(c => ciStr(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
// always_visible_tabs
|
// always_visible_tabs
|
||||||
@@ -953,6 +953,10 @@ document.addEventListener('DOMContentLoaded', async function() {
|
|||||||
loadAllComponents(),
|
loadAllComponents(),
|
||||||
categoriesPromise,
|
categoriesPromise,
|
||||||
]);
|
]);
|
||||||
|
cart = cart.map(item => ({
|
||||||
|
...item,
|
||||||
|
category: item.category || allComponents.find(c => c.lot_name.toUpperCase() === (item.lot_name || '').toUpperCase())?.category || ''
|
||||||
|
}));
|
||||||
syncPriceSettingsControls();
|
syncPriceSettingsControls();
|
||||||
renderPricelistSettingsSummary();
|
renderPricelistSettingsSummary();
|
||||||
updateRefreshPricesButtonState();
|
updateRefreshPricesButtonState();
|
||||||
@@ -1218,14 +1222,16 @@ function applyPriceSettings() {
|
|||||||
schedulePriceLevelsRefresh({ delay: 0, rerender: true, autosave: true });
|
schedulePriceLevelsRefresh({ delay: 0, rerender: true, autosave: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ciStr(s) { return (s || '').toLowerCase(); }
|
||||||
|
|
||||||
function getComponentCategory(comp) {
|
function getComponentCategory(comp) {
|
||||||
return (comp.category || '').toUpperCase();
|
return comp.category || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTabForCategory(category) {
|
function getTabForCategory(category) {
|
||||||
const cat = category.toUpperCase();
|
const cat = ciStr(category);
|
||||||
for (const [tabKey, tabConfig] of Object.entries(TAB_CONFIG)) {
|
for (const [tabKey, tabConfig] of Object.entries(TAB_CONFIG)) {
|
||||||
if (tabConfig.categories.map(c => c.toUpperCase()).includes(cat)) {
|
if (tabConfig.categories.some(c => ciStr(c) === cat)) {
|
||||||
return tabKey;
|
return tabKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1306,7 +1312,7 @@ function applyConfigTypeToTabs() {
|
|||||||
// Rebuild assigned categories index
|
// Rebuild assigned categories index
|
||||||
ASSIGNED_CATEGORIES = Object.values(TAB_CONFIG)
|
ASSIGNED_CATEGORIES = Object.values(TAB_CONFIG)
|
||||||
.flatMap(t => t.categories)
|
.flatMap(t => t.categories)
|
||||||
.map(c => c.toUpperCase());
|
.map(c => ciStr(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateTabVisibility() {
|
function updateTabVisibility() {
|
||||||
@@ -1334,15 +1340,15 @@ function getComponentsForTab(tab) {
|
|||||||
return allComponents.filter(comp => {
|
return allComponents.filter(comp => {
|
||||||
const category = getComponentCategory(comp);
|
const category = getComponentCategory(comp);
|
||||||
if (tab === 'other') {
|
if (tab === 'other') {
|
||||||
return !ASSIGNED_CATEGORIES.includes(category);
|
return !ASSIGNED_CATEGORIES.includes(ciStr(category));
|
||||||
}
|
}
|
||||||
return config.categories.map(c => c.toUpperCase()).includes(category);
|
return config.categories.some(c => ciStr(c) === ciStr(category));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getComponentsForCategory(category) {
|
function getComponentsForCategory(category) {
|
||||||
return allComponents.filter(comp => {
|
return allComponents.filter(comp => {
|
||||||
return getComponentCategory(comp) === category.toUpperCase();
|
return ciStr(getComponentCategory(comp)) === ciStr(category);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1404,7 +1410,7 @@ function renderSingleSelectTab(categories) {
|
|||||||
categories.forEach(cat => {
|
categories.forEach(cat => {
|
||||||
const catLabel = cat === 'MB' ? 'MB' : cat === 'CPU' ? 'CPU' : cat === 'MEM' ? 'MEM' : cat;
|
const catLabel = cat === 'MB' ? 'MB' : cat === 'CPU' ? 'CPU' : cat === 'MEM' ? 'MEM' : cat;
|
||||||
const selectedItem = cart.find(item =>
|
const selectedItem = cart.find(item =>
|
||||||
(item.category).toUpperCase() === cat.toUpperCase()
|
ciStr(item.category) === ciStr(cat)
|
||||||
);
|
);
|
||||||
|
|
||||||
const comp = selectedItem ? allComponents.find(c => c.lot_name.toUpperCase() === (selectedItem.lot_name || '').toUpperCase()) : null;
|
const comp = selectedItem ? allComponents.find(c => c.lot_name.toUpperCase() === (selectedItem.lot_name || '').toUpperCase()) : null;
|
||||||
@@ -1457,9 +1463,7 @@ function renderSingleSelectTab(categories) {
|
|||||||
function renderMultiSelectTab(components) {
|
function renderMultiSelectTab(components) {
|
||||||
// Get cart items for this tab
|
// Get cart items for this tab
|
||||||
const tabItems = cart.filter(item => {
|
const tabItems = cart.filter(item => {
|
||||||
const cat = (item.category).toUpperCase();
|
return getTabForCategory(item.category) === currentTab;
|
||||||
const tab = getTabForCategory(cat);
|
|
||||||
return tab === currentTab;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let html = `
|
let html = `
|
||||||
@@ -1546,9 +1550,7 @@ function renderMultiSelectTab(components) {
|
|||||||
function renderMultiSelectTabWithSections(sections) {
|
function renderMultiSelectTabWithSections(sections) {
|
||||||
// Get cart items for this tab
|
// Get cart items for this tab
|
||||||
const tabItems = cart.filter(item => {
|
const tabItems = cart.filter(item => {
|
||||||
const cat = (item.category).toUpperCase();
|
return getTabForCategory(item.category) === currentTab;
|
||||||
const tab = getTabForCategory(cat);
|
|
||||||
return tab === currentTab;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let html = '';
|
let html = '';
|
||||||
@@ -1556,17 +1558,14 @@ function renderMultiSelectTabWithSections(sections) {
|
|||||||
|
|
||||||
sections.forEach((section, sectionIdx) => {
|
sections.forEach((section, sectionIdx) => {
|
||||||
// Get components for this section's categories
|
// Get components for this section's categories
|
||||||
const sectionCategories = section.categories.map(c => c.toUpperCase());
|
|
||||||
const sectionComponents = allComponents.filter(comp => {
|
const sectionComponents = allComponents.filter(comp => {
|
||||||
const category = getComponentCategory(comp);
|
return section.categories.some(c => ciStr(c) === ciStr(getComponentCategory(comp)));
|
||||||
return sectionCategories.includes(category);
|
|
||||||
});
|
});
|
||||||
totalComponents += sectionComponents.length;
|
totalComponents += sectionComponents.length;
|
||||||
|
|
||||||
// Get cart items for this section
|
// Get cart items for this section
|
||||||
const sectionItems = tabItems.filter(item => {
|
const sectionItems = tabItems.filter(item => {
|
||||||
const cat = (item.category).toUpperCase();
|
return sectionCategories.some(c => ciStr(c) === ciStr(item.category));
|
||||||
return sectionCategories.includes(cat);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Section header
|
// Section header
|
||||||
@@ -1806,7 +1805,7 @@ function selectAutocompleteItem(index) {
|
|||||||
|
|
||||||
// Remove existing item of this category
|
// Remove existing item of this category
|
||||||
cart = cart.filter(item =>
|
cart = cart.filter(item =>
|
||||||
(item.category).toUpperCase() !== autocompleteCategory.toUpperCase()
|
ciStr(item.category) !== ciStr(autocompleteCategory)
|
||||||
);
|
);
|
||||||
|
|
||||||
const qtyInput = document.getElementById('qty-' + autocompleteCategory);
|
const qtyInput = document.getElementById('qty-' + autocompleteCategory);
|
||||||
@@ -2189,7 +2188,7 @@ function selectAutocompleteItemBOM(index, rowIdx) {
|
|||||||
|
|
||||||
function clearSingleSelect(category) {
|
function clearSingleSelect(category) {
|
||||||
cart = cart.filter(item =>
|
cart = cart.filter(item =>
|
||||||
(item.category).toUpperCase() !== category.toUpperCase()
|
ciStr(item.category) !== ciStr(category)
|
||||||
);
|
);
|
||||||
renderTab();
|
renderTab();
|
||||||
updateCartUI();
|
updateCartUI();
|
||||||
@@ -2199,7 +2198,7 @@ function clearSingleSelect(category) {
|
|||||||
function updateSingleQuantity(category, value) {
|
function updateSingleQuantity(category, value) {
|
||||||
const qty = parseInt(value) || 1;
|
const qty = parseInt(value) || 1;
|
||||||
const item = cart.find(i =>
|
const item = cart.find(i =>
|
||||||
(i.category).toUpperCase() === category.toUpperCase()
|
ciStr(i.category) === ciStr(category)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (item) {
|
if (item) {
|
||||||
@@ -2258,8 +2257,8 @@ function updateCartUI() {
|
|||||||
|
|
||||||
// Sort cart items by category display order
|
// Sort cart items by category display order
|
||||||
const sortedCart = [...cart].sort((a, b) => {
|
const sortedCart = [...cart].sort((a, b) => {
|
||||||
const catA = (a.category).toUpperCase();
|
const catA = ciStr(a.category);
|
||||||
const catB = (b.category).toUpperCase();
|
const catB = ciStr(b.category);
|
||||||
const orderA = categoryOrderMap[catA] || 9999;
|
const orderA = categoryOrderMap[catA] || 9999;
|
||||||
const orderB = categoryOrderMap[catB] || 9999;
|
const orderB = categoryOrderMap[catB] || 9999;
|
||||||
return orderA - orderB;
|
return orderA - orderB;
|
||||||
@@ -2267,8 +2266,7 @@ function updateCartUI() {
|
|||||||
|
|
||||||
const grouped = {};
|
const grouped = {};
|
||||||
sortedCart.forEach(item => {
|
sortedCart.forEach(item => {
|
||||||
const cat = item.category;
|
const tab = getTabForCategory(item.category);
|
||||||
const tab = getTabForCategory(cat);
|
|
||||||
if (!grouped[tab]) grouped[tab] = [];
|
if (!grouped[tab]) grouped[tab] = [];
|
||||||
grouped[tab].push(item);
|
grouped[tab].push(item);
|
||||||
});
|
});
|
||||||
@@ -2276,11 +2274,11 @@ function updateCartUI() {
|
|||||||
// Sort tabs by minimum display order of their categories
|
// Sort tabs by minimum display order of their categories
|
||||||
const sortedTabs = Object.entries(grouped).sort((a, b) => {
|
const sortedTabs = Object.entries(grouped).sort((a, b) => {
|
||||||
const minOrderA = Math.min(...a[1].map(item => {
|
const minOrderA = Math.min(...a[1].map(item => {
|
||||||
const cat = (item.category).toUpperCase();
|
const cat = ciStr(item.category);
|
||||||
return categoryOrderMap[cat] || 9999;
|
return categoryOrderMap[cat] || 9999;
|
||||||
}));
|
}));
|
||||||
const minOrderB = Math.min(...b[1].map(item => {
|
const minOrderB = Math.min(...b[1].map(item => {
|
||||||
const cat = (item.category).toUpperCase();
|
const cat = ciStr(item.category);
|
||||||
return categoryOrderMap[cat] || 9999;
|
return categoryOrderMap[cat] || 9999;
|
||||||
}));
|
}));
|
||||||
return minOrderA - minOrderB;
|
return minOrderA - minOrderB;
|
||||||
@@ -2731,8 +2729,8 @@ function renderSalePriceTable() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sortedCart = [...cart].sort((a, b) => {
|
const sortedCart = [...cart].sort((a, b) => {
|
||||||
const catA = (a.category).toUpperCase();
|
const catA = ciStr(a.category);
|
||||||
const catB = (b.category).toUpperCase();
|
const catB = ciStr(b.category);
|
||||||
const orderA = categoryOrderMap[catA] || 9999;
|
const orderA = categoryOrderMap[catA] || 9999;
|
||||||
const orderB = categoryOrderMap[catB] || 9999;
|
const orderB = categoryOrderMap[catB] || 9999;
|
||||||
return orderA - orderB;
|
return orderA - orderB;
|
||||||
@@ -2835,8 +2833,8 @@ function calculateCustomPrice() {
|
|||||||
// Build adjusted prices table
|
// Build adjusted prices table
|
||||||
// Sort cart items by category display order
|
// Sort cart items by category display order
|
||||||
const sortedCart = [...cart].sort((a, b) => {
|
const sortedCart = [...cart].sort((a, b) => {
|
||||||
const catA = (a.category).toUpperCase();
|
const catA = ciStr(a.category);
|
||||||
const catB = (b.category).toUpperCase();
|
const catB = ciStr(b.category);
|
||||||
const orderA = categoryOrderMap[catA] || 9999;
|
const orderA = categoryOrderMap[catA] || 9999;
|
||||||
const orderB = categoryOrderMap[catB] || 9999;
|
const orderB = categoryOrderMap[catB] || 9999;
|
||||||
return orderA - orderB;
|
return orderA - orderB;
|
||||||
@@ -4146,8 +4144,8 @@ async function renderPricingTab() {
|
|||||||
|
|
||||||
if (!bomRows.length) {
|
if (!bomRows.length) {
|
||||||
const sortedByCategory = [...cart].sort((a, b) => {
|
const sortedByCategory = [...cart].sort((a, b) => {
|
||||||
const catA = (a.category).toUpperCase();
|
const catA = ciStr(a.category);
|
||||||
const catB = (b.category).toUpperCase();
|
const catB = ciStr(b.category);
|
||||||
return (categoryOrderMap[catA] || 9999) - (categoryOrderMap[catB] || 9999);
|
return (categoryOrderMap[catA] || 9999) - (categoryOrderMap[catB] || 9999);
|
||||||
});
|
});
|
||||||
sortedByCategory.forEach(item => { _pushCartRow(item, false); coveredLots.add(item.lot_name); });
|
sortedByCategory.forEach(item => { _pushCartRow(item, false); coveredLots.add(item.lot_name); });
|
||||||
|
|||||||
Reference in New Issue
Block a user