fix(wizard): store material name cleanly instead of brand+category+specs
Adding a new item wrote the material name as 'brand + category + specs' (e.g. 'پارس لوله لوله PE100 سایز ۱۶'), while specs were also stored correctly in EAV attributes. The wizard then tried to recover the base name by stripping brand and specs out of that string, but never stripped the injected category — so the spec stayed baked into the cached material name and the مشخصه appeared as part of the material name on later visits. - api.ts / WizardPage / EditModal: build name from the material itself only (brand goes to brand_ids, specs to attributes) - materialBaseName: strip category too, never strip a part that would empty the name, and reject a spec-only result; legacy rows whose name cannot be disentangled fall back to the stored name rather than a guess - WizardPage: match existing materials on category+brand so legacy rows are still reused instead of duplicated
This commit is contained in:
@@ -1041,8 +1041,10 @@ export const api = {
|
||||
const unitId = await unitIdByNameOrCreate(lookup, unitName);
|
||||
// برند — اگر تعریف نشده، در جدول brands ساخته و به متریال متصل میشود
|
||||
const brandId = await brandIdByNameOrCreate(lookup, m.brand);
|
||||
// تایپ جلوی برنامه name ندارد — از متریال/برند/مشخصه ساخته میشود
|
||||
const name = [m.brand, m.category, ...(m.specs || [])].filter(Boolean).join(" ") || "کالای جدید";
|
||||
// name فقط نامِ خودِ متریال است؛ برند در brand_ids و مشخصهها در
|
||||
// attributes (EAV) ذخیره میشوند. اگر برند/مشخصه داخل name ریخته شود،
|
||||
// در ویزارد بهعنوان «نامِ متریال» کش میشود و مشخصهها در نام ظاهر میشوند.
|
||||
const name = (m.category || m.brand || (m.specs || []).join(" ") || "").trim() || "کالای جدید";
|
||||
// مشخصهها بهصورت attribute (EAV) ذخیره میشوند تا بعد از رفرش دیده شوند
|
||||
const attrs: { attribute_definition_id: string; value: string }[] = [];
|
||||
const seen = new Set<string>();
|
||||
|
||||
@@ -299,7 +299,7 @@ export function EditModal({ quoteId, onClose }: EditModalProps) {
|
||||
} else {
|
||||
const nm = {
|
||||
id: nextId("m", materialsCopy),
|
||||
name: [brand, cat, ...specs].filter(Boolean).join(" ") || "کالای جدید",
|
||||
name: (cat || brand || specs.join(" ") || "").trim() || "کالای جدید",
|
||||
topic: materialById(materials, row.origLi?.material_id || "").topic || "",
|
||||
category: cat,
|
||||
brand,
|
||||
|
||||
@@ -41,21 +41,30 @@ export function itemRowLabel(it: ItemDraft): string {
|
||||
return qtyPart ? `${name} -- ${qtyPart}` : name;
|
||||
}
|
||||
|
||||
// نام پایهی متریال از نامِ ذخیرهشده
|
||||
function materialBaseName(name: string, brand: string, specs: string[]): string {
|
||||
let base = (name || "").trim();
|
||||
if (!base) return "";
|
||||
if (brand) {
|
||||
if (base.startsWith(brand)) base = base.slice(brand.length);
|
||||
else if (base.includes(brand)) base = base.split(brand).join(" ");
|
||||
// نام پایهی متریال از نامِ ذخیرهشده.
|
||||
// متریالهای جدید فقط نامِ خودِ متریال را در name دارند (برند در brand_ids و
|
||||
// مشخصهها در attributes ذخیره میشوند) و دستنخورده برمیگردند. رکوردهای قدیمی
|
||||
// name را به شکل «برند + دسته + مشخصه» ذخیره کردهاند؛ آن قطعات جدا میشوند تا
|
||||
// نامِ پایه باقی بماند.
|
||||
function materialBaseName(name: string, brand: string, specs: string[], category = ""): string {
|
||||
const fallback = (name || "").trim();
|
||||
if (!fallback) return "";
|
||||
let base = fallback;
|
||||
// بلندترین قطعه اول حذف میشود تا حذفهای جزئی، رشته را ناقص نکنند
|
||||
const parts = [brand, category, ...specs]
|
||||
.map((p) => (p || "").trim())
|
||||
.filter(Boolean)
|
||||
.sort((a, b) => b.length - a.length);
|
||||
for (const p of parts) {
|
||||
if (!base.includes(p)) continue;
|
||||
const next = base.split(p).join(" ").replace(/\s+/g, " ").trim();
|
||||
// اگر حذفِ این قطعه کلِ نام را خالی کند، یعنی همان نامِ متریال بوده — نگهش دار
|
||||
if (!next) continue;
|
||||
base = next;
|
||||
}
|
||||
specs
|
||||
.filter((s) => s && s.trim())
|
||||
.forEach((s) => {
|
||||
if (base.includes(s)) base = base.split(s).join(" ");
|
||||
});
|
||||
base = base.replace(/\s+/g, " ").trim();
|
||||
return base || (name || "").trim();
|
||||
// شکلِ قدیمیِ خراب میتواند فقط یک مشخصه باقی بگذارد؛ آن دیگر نامِ متریال نیست
|
||||
if (parts.some((p) => p === base)) return fallback;
|
||||
return base || fallback;
|
||||
}
|
||||
|
||||
// مقادیر مجزای category/brand/spec داخل تاپیک
|
||||
@@ -119,7 +128,7 @@ export function Step2LineItems({ items, onItemsChange, selectedTopic, onRemember
|
||||
const allMaterialNames = useMemo(() => {
|
||||
const set = new Set<string>();
|
||||
materials.forEach((m) => {
|
||||
const base = materialBaseName(m.name, m.brand, m.specs);
|
||||
const base = materialBaseName(m.name, m.brand, m.specs, m.category);
|
||||
if (base) set.add(base);
|
||||
});
|
||||
return [...set].sort((a, b) => a.localeCompare(b, "fa"));
|
||||
|
||||
@@ -128,7 +128,7 @@ export function WizardPage() {
|
||||
const rememberMaterial = (rec: MaterialRec) => {
|
||||
void addMaterial.mutateAsync({
|
||||
id: nextId("m", materials),
|
||||
name: [rec.brand, rec.category, ...rec.specs].filter(Boolean).join(" ") || "کالای جدید",
|
||||
name: (rec.category || rec.brand || rec.specs.join(" ") || "").trim() || "کالای جدید",
|
||||
topic: rec.topic,
|
||||
category: rec.category,
|
||||
brand: rec.brand,
|
||||
@@ -243,7 +243,7 @@ export function WizardPage() {
|
||||
const total = it.total;
|
||||
if (!cat && !brand && qty == null && price == null) continue;
|
||||
|
||||
const wantName = [brand, cat, ...specs].filter(Boolean).join(" ") || "کالای جدید";
|
||||
const wantName = (cat || brand || specs.join(" ") || "").trim() || "کالای جدید";
|
||||
let material = materials.find((m) => normalize(m.name) === normalize(wantName));
|
||||
if (!material) {
|
||||
material = materials.find(
|
||||
@@ -253,6 +253,13 @@ export function WizardPage() {
|
||||
specsKey(m.specs) === specsKey(specs),
|
||||
);
|
||||
}
|
||||
// رکوردهای قدیمی name را به شکل «برند + دسته + مشخصه» ذخیره کردهاند؛
|
||||
// تطبیق دوم بر اساس category/brand/specs انجام میشود تا آنها هم پیدا شوند.
|
||||
if (!material && cat) {
|
||||
material = materials.find(
|
||||
(m) => normalize(m.category) === normalize(cat) && normalize(m.brand) === normalize(brand),
|
||||
);
|
||||
}
|
||||
let materialId: string;
|
||||
if (material) {
|
||||
materialId = material.id;
|
||||
|
||||
Reference in New Issue
Block a user