@open-mercato/core 0.6.1-develop.3291.1.6fad645fd0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +14 -0
- package/dist/modules/auth/backend/roles/[id]/edit/page.js +2 -1
- package/dist/modules/auth/backend/roles/[id]/edit/page.js.map +2 -2
- package/dist/modules/auth/backend/users/[id]/edit/page.js +2 -5
- package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
- package/dist/modules/catalog/ai-agents.js +47 -5
- package/dist/modules/catalog/ai-agents.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js +2 -5
- package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js +4 -3
- package/dist/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js +2 -9
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +2 -9
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
- package/dist/modules/customers/ai-agents.js +174 -1
- package/dist/modules/customers/ai-agents.js.map +2 -2
- package/dist/modules/customers/ai-tools/deal-analyzer-pack.js +196 -0
- package/dist/modules/customers/ai-tools/deal-analyzer-pack.js.map +7 -0
- package/dist/modules/customers/ai-tools.js +3 -1
- package/dist/modules/customers/ai-tools.js.map +2 -2
- package/dist/modules/customers/backend/customers/companies/page.js +11 -1
- package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/page.js +11 -1
- package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
- package/dist/modules/customers/backend/customers/people/page.js +11 -1
- package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
- package/dist/modules/customers/commands/companies.js +37 -15
- package/dist/modules/customers/commands/companies.js.map +2 -2
- package/dist/modules/customers/components/detail/AiActionChips.js +2 -2
- package/dist/modules/customers/components/detail/AiActionChips.js.map +2 -2
- package/dist/modules/customers/components/detail/ChangelogTab.js +3 -9
- package/dist/modules/customers/components/detail/ChangelogTab.js.map +2 -2
- package/dist/modules/customers/components/detail/changelogValues.js +35 -0
- package/dist/modules/customers/components/detail/changelogValues.js.map +7 -0
- package/dist/modules/customers/components/detail/customFieldUtils.js +1 -1
- package/dist/modules/customers/components/detail/customFieldUtils.js.map +1 -1
- package/dist/modules/customers/components/formConfig.js +3 -2
- package/dist/modules/customers/components/formConfig.js.map +2 -2
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js +4 -9
- package/dist/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.js.map +2 -2
- package/dist/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.client.js +302 -0
- package/dist/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.client.js.map +7 -0
- package/dist/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.js +17 -0
- package/dist/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.js.map +7 -0
- package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.js +2 -2
- package/dist/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.js.map +2 -2
- package/dist/modules/customers/widgets/injection-table.js +2 -2
- package/dist/modules/customers/widgets/injection-table.js.map +2 -2
- package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js +2 -5
- package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js.map +2 -2
- package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js +2 -5
- package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js.map +2 -2
- package/dist/modules/resources/backend/resources/resources/[id]/page.js +2 -5
- package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
- package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js +2 -25
- package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js.map +2 -2
- package/dist/modules/sales/components/channels/ChannelOfferForm.js +2 -43
- package/dist/modules/sales/components/channels/ChannelOfferForm.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +162 -42
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/customFieldHelpers.js +4 -31
- package/dist/modules/sales/components/documents/customFieldHelpers.js.map +2 -2
- package/package.json +16 -8
- package/src/modules/auth/backend/roles/[id]/edit/page.tsx +2 -1
- package/src/modules/auth/backend/users/[id]/edit/page.tsx +2 -5
- package/src/modules/business_rules/migrations/.snapshot-open-mercato.json +746 -394
- package/src/modules/catalog/ai-agents.ts +62 -4
- package/src/modules/catalog/backend/catalog/categories/[id]/edit/page.tsx +2 -5
- package/src/modules/catalog/backend/catalog/products/MerchandisingAssistantSheet.tsx +4 -3
- package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +2 -12
- package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +2 -10
- package/src/modules/catalog/migrations/.snapshot-open-mercato.json +2519 -1307
- package/src/modules/customer_accounts/migrations/.snapshot-open-mercato.json +828 -834
- package/src/modules/customers/AGENTS.md +6 -1
- package/src/modules/customers/ai-agents.ts +213 -6
- package/src/modules/customers/ai-tools/deal-analyzer-pack.ts +286 -0
- package/src/modules/customers/ai-tools.ts +2 -0
- package/src/modules/customers/backend/customers/companies/page.tsx +10 -0
- package/src/modules/customers/backend/customers/deals/page.tsx +10 -0
- package/src/modules/customers/backend/customers/people/page.tsx +10 -0
- package/src/modules/customers/commands/companies.ts +38 -14
- package/src/modules/customers/components/detail/AiActionChips.tsx +2 -2
- package/src/modules/customers/components/detail/ChangelogTab.tsx +3 -10
- package/src/modules/customers/components/detail/changelogValues.ts +42 -0
- package/src/modules/customers/components/detail/customFieldUtils.ts +1 -1
- package/src/modules/customers/components/formConfig.tsx +3 -2
- package/src/modules/customers/i18n/de.json +7 -0
- package/src/modules/customers/i18n/en.json +7 -0
- package/src/modules/customers/i18n/es.json +7 -0
- package/src/modules/customers/i18n/pl.json +7 -0
- package/src/modules/customers/migrations/.snapshot-open-mercato.json +2313 -2336
- package/src/modules/customers/widgets/injection/ai-assistant-trigger/widget.client.tsx +4 -6
- package/src/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.client.tsx +358 -0
- package/src/modules/customers/widgets/injection/ai-deal-analyzer-trigger/widget.ts +34 -0
- package/src/modules/customers/widgets/injection/ai-deal-detail-trigger/widget.client.tsx +2 -2
- package/src/modules/customers/widgets/injection-table.ts +12 -9
- package/src/modules/data_sync/migrations/.snapshot-open-mercato.json +554 -210
- package/src/modules/dictionaries/migrations/.snapshot-open-mercato.json +146 -146
- package/src/modules/directory/backend/directory/organizations/[id]/edit/page.tsx +2 -5
- package/src/modules/directory/backend/directory/tenants/[id]/edit/page.tsx +2 -5
- package/src/modules/directory/migrations/.snapshot-open-mercato.json +273 -140
- package/src/modules/feature_toggles/migrations/.snapshot-open-mercato.json +242 -137
- package/src/modules/integrations/migrations/.snapshot-open-mercato.json +498 -195
- package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +158 -157
- package/src/modules/perspectives/migrations/.snapshot-open-mercato.json +274 -129
- package/src/modules/planner/migrations/.snapshot-open-mercato.json +327 -157
- package/src/modules/progress/AGENTS.md +72 -0
- package/src/modules/resources/backend/resources/resources/[id]/page.tsx +2 -5
- package/src/modules/resources/migrations/.snapshot-open-mercato.json +920 -470
- package/src/modules/sales/backend/sales/channels/[channelId]/edit/page.tsx +2 -27
- package/src/modules/sales/components/channels/ChannelOfferForm.tsx +2 -49
- package/src/modules/sales/components/documents/LineItemDialog.tsx +214 -78
- package/src/modules/sales/components/documents/customFieldHelpers.ts +4 -43
- package/src/modules/sales/i18n/de.json +3 -0
- package/src/modules/sales/i18n/en.json +3 -0
- package/src/modules/sales/i18n/es.json +3 -0
- package/src/modules/sales/i18n/pl.json +3 -0
- package/src/modules/sales/migrations/.snapshot-open-mercato.json +4740 -4778
- package/src/modules/shipping_carriers/migrations/.snapshot-open-mercato.json +340 -81
- package/src/modules/staff/migrations/.snapshot-open-mercato.json +1439 -729
|
@@ -9,6 +9,7 @@ import { updateCrud, deleteCrud } from '@open-mercato/ui/backend/utils/crud'
|
|
|
9
9
|
import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
|
|
10
10
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
11
11
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
12
|
+
import { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'
|
|
12
13
|
import { useChannelFields, buildChannelPayload, type ChannelFormValues } from '@open-mercato/core/modules/sales/components/channels/channelFormFields'
|
|
13
14
|
import { E } from '#generated/entities.ids.generated'
|
|
14
15
|
import { SalesChannelOffersPanel } from '@open-mercato/core/modules/sales/components/channels/SalesChannelOffersPanel'
|
|
@@ -196,31 +197,5 @@ function mapChannelToFormValues(item: Record<string, unknown>): ChannelFormValue
|
|
|
196
197
|
: null,
|
|
197
198
|
isActive: item.isActive === true || item.is_active === true,
|
|
198
199
|
}
|
|
199
|
-
|
|
200
|
-
if (!source || typeof source !== 'object' || Array.isArray(source)) return
|
|
201
|
-
for (const [key, value] of Object.entries(source as Record<string, unknown>)) {
|
|
202
|
-
if (!key) continue
|
|
203
|
-
values[`cf_${key}`] = value
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
const mergeCustomArray = (source: unknown) => {
|
|
207
|
-
if (!Array.isArray(source)) return
|
|
208
|
-
source.forEach((entry) => {
|
|
209
|
-
if (!entry || typeof entry !== 'object') return
|
|
210
|
-
const key = typeof (entry as Record<string, unknown>).key === 'string'
|
|
211
|
-
? (entry as Record<string, unknown>).key
|
|
212
|
-
: null
|
|
213
|
-
if (!key) return
|
|
214
|
-
values[`cf_${key}`] = (entry as Record<string, unknown>).value
|
|
215
|
-
})
|
|
216
|
-
}
|
|
217
|
-
mergeCustomObject(item.customValues)
|
|
218
|
-
mergeCustomObject((item as Record<string, unknown>).custom_values)
|
|
219
|
-
mergeCustomObject(item.customFields)
|
|
220
|
-
mergeCustomObject((item as Record<string, unknown>).custom_fields)
|
|
221
|
-
mergeCustomArray(item.customFields)
|
|
222
|
-
mergeCustomArray((item as Record<string, unknown>).custom_fields)
|
|
223
|
-
mergeCustomArray((item as Record<string, unknown>).customFieldEntries)
|
|
224
|
-
mergeCustomArray((item as Record<string, unknown>).custom_field_entries)
|
|
225
|
-
return values
|
|
200
|
+
return { ...values, ...extractCustomFieldEntries(item) }
|
|
226
201
|
}
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from '@open-mercato/ui/primitives/select'
|
|
21
21
|
import { Loader2, Search, Image as ImageIcon, Trash2 } from 'lucide-react'
|
|
22
22
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
23
|
+
import { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'
|
|
23
24
|
import { E } from '#generated/entities.ids.generated'
|
|
24
25
|
import { buildAttachmentImageUrl, slugifyAttachmentFileName } from '@open-mercato/core/modules/attachments/lib/imageUrls'
|
|
25
26
|
import { cn } from '@open-mercato/shared/lib/utils'
|
|
@@ -753,55 +754,7 @@ function collectPriceIds(source: PriceOverrideDraft[] | null | undefined): Set<s
|
|
|
753
754
|
}
|
|
754
755
|
|
|
755
756
|
function mergeCustomFieldValues(target: Record<string, unknown>, source: Record<string, unknown> | null | undefined) {
|
|
756
|
-
|
|
757
|
-
const assign = (key: string | null | undefined, value: unknown) => {
|
|
758
|
-
if (!key) return
|
|
759
|
-
target[`cf_${key}`] = value
|
|
760
|
-
}
|
|
761
|
-
for (const [rawKey, rawValue] of Object.entries(source)) {
|
|
762
|
-
if (rawKey.startsWith('cf_')) {
|
|
763
|
-
if (rawKey.endsWith('__is_multi')) continue
|
|
764
|
-
target[rawKey] = rawValue
|
|
765
|
-
} else if (rawKey.startsWith('cf:')) {
|
|
766
|
-
assign(rawKey.slice(3), rawValue)
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
const customValues =
|
|
770
|
-
(source as Record<string, unknown>).customValues ??
|
|
771
|
-
(source as Record<string, unknown>).custom_values
|
|
772
|
-
if (customValues && typeof customValues === 'object' && !Array.isArray(customValues)) {
|
|
773
|
-
for (const [key, value] of Object.entries(customValues as Record<string, unknown>)) {
|
|
774
|
-
assign(key, value)
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
const customFields =
|
|
778
|
-
(source as Record<string, unknown>).customFields ??
|
|
779
|
-
(source as Record<string, unknown>).custom_fields
|
|
780
|
-
if (Array.isArray(customFields)) {
|
|
781
|
-
customFields.forEach((entry) => {
|
|
782
|
-
if (!entry || typeof entry !== 'object') return
|
|
783
|
-
const entryRecord = entry as Record<string, unknown>
|
|
784
|
-
const key = typeof entryRecord.key === 'string' ? entryRecord.key : null
|
|
785
|
-
if (!key) return
|
|
786
|
-
assign(key, entryRecord.value)
|
|
787
|
-
})
|
|
788
|
-
} else if (customFields && typeof customFields === 'object') {
|
|
789
|
-
for (const [key, value] of Object.entries(customFields as Record<string, unknown>)) {
|
|
790
|
-
assign(key, value)
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
const customEntries =
|
|
794
|
-
(source as Record<string, unknown>).customFieldEntries ??
|
|
795
|
-
(source as Record<string, unknown>).custom_field_entries
|
|
796
|
-
if (Array.isArray(customEntries)) {
|
|
797
|
-
customEntries.forEach((entry) => {
|
|
798
|
-
if (!entry || typeof entry !== 'object') return
|
|
799
|
-
const entryRecord = entry as Record<string, unknown>
|
|
800
|
-
const key = typeof entryRecord.key === 'string' ? entryRecord.key : null
|
|
801
|
-
if (!key) return
|
|
802
|
-
assign(key, entryRecord.value)
|
|
803
|
-
})
|
|
804
|
-
}
|
|
757
|
+
Object.assign(target, extractCustomFieldEntries(source ?? {}))
|
|
805
758
|
}
|
|
806
759
|
|
|
807
760
|
function buildChannelOffersHref(channelId?: string | null): string {
|
|
@@ -23,6 +23,11 @@ import {
|
|
|
23
23
|
} from "@open-mercato/ui/primitives/dialog";
|
|
24
24
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
25
25
|
import { Input } from "@open-mercato/ui/primitives/input";
|
|
26
|
+
import {
|
|
27
|
+
Alert,
|
|
28
|
+
AlertDescription,
|
|
29
|
+
AlertTitle,
|
|
30
|
+
} from "@open-mercato/ui/primitives/alert";
|
|
26
31
|
import {
|
|
27
32
|
Select,
|
|
28
33
|
SelectContent,
|
|
@@ -350,6 +355,71 @@ function normalizeUnitPriceInputValue(value: number): string {
|
|
|
350
355
|
return rounded.toString();
|
|
351
356
|
}
|
|
352
357
|
|
|
358
|
+
function mapProductOption(item: Record<string, unknown>): ProductOption | null {
|
|
359
|
+
const id = typeof item.id === "string" ? item.id : null;
|
|
360
|
+
if (!id) return null;
|
|
361
|
+
const productItem = item as ApiProductItem;
|
|
362
|
+
const title =
|
|
363
|
+
typeof item.title === "string"
|
|
364
|
+
? item.title
|
|
365
|
+
: typeof productItem.name === "string"
|
|
366
|
+
? productItem.name
|
|
367
|
+
: id;
|
|
368
|
+
const sku = typeof productItem.sku === "string" ? productItem.sku : null;
|
|
369
|
+
const thumbnail =
|
|
370
|
+
typeof productItem.default_media_url === "string"
|
|
371
|
+
? productItem.default_media_url
|
|
372
|
+
: typeof productItem.defaultMediaUrl === "string"
|
|
373
|
+
? productItem.defaultMediaUrl
|
|
374
|
+
: null;
|
|
375
|
+
const pricing =
|
|
376
|
+
typeof productItem.pricing === "object" && productItem.pricing
|
|
377
|
+
? productItem.pricing
|
|
378
|
+
: null;
|
|
379
|
+
const metadata =
|
|
380
|
+
typeof productItem.metadata === "object" && productItem.metadata
|
|
381
|
+
? productItem.metadata
|
|
382
|
+
: null;
|
|
383
|
+
const pricingMeta = pricing as ApiPricingMetadata | null;
|
|
384
|
+
const metaMeta = metadata as ApiPricingMetadata | null;
|
|
385
|
+
const pricingTaxRateId =
|
|
386
|
+
typeof pricingMeta?.tax_rate_id === "string" &&
|
|
387
|
+
pricingMeta.tax_rate_id.trim().length
|
|
388
|
+
? pricingMeta.tax_rate_id.trim()
|
|
389
|
+
: typeof pricingMeta?.taxRateId === "string" &&
|
|
390
|
+
pricingMeta.taxRateId.trim().length
|
|
391
|
+
? pricingMeta.taxRateId.trim()
|
|
392
|
+
: null;
|
|
393
|
+
const metaTaxRateId =
|
|
394
|
+
typeof metaMeta?.taxRateId === "string" && metaMeta.taxRateId.trim().length
|
|
395
|
+
? metaMeta.taxRateId.trim()
|
|
396
|
+
: typeof metaMeta?.tax_rate_id === "string" &&
|
|
397
|
+
metaMeta.tax_rate_id.trim().length
|
|
398
|
+
? metaMeta.tax_rate_id.trim()
|
|
399
|
+
: null;
|
|
400
|
+
const taxRateValue = normalizeNumber(
|
|
401
|
+
pricingMeta?.tax_rate ??
|
|
402
|
+
pricingMeta?.taxRate ??
|
|
403
|
+
productItem.tax_rate ??
|
|
404
|
+
productItem.taxRate,
|
|
405
|
+
Number.NaN,
|
|
406
|
+
);
|
|
407
|
+
const uomFields = getUomProductFields(item);
|
|
408
|
+
return {
|
|
409
|
+
id,
|
|
410
|
+
title,
|
|
411
|
+
sku,
|
|
412
|
+
thumbnailUrl: thumbnail,
|
|
413
|
+
taxRateId: pricingTaxRateId ?? metaTaxRateId ?? null,
|
|
414
|
+
taxRate: Number.isFinite(taxRateValue) ? taxRateValue : null,
|
|
415
|
+
defaultUnit: uomFields.defaultUnit,
|
|
416
|
+
defaultSalesUnit: uomFields.defaultSalesUnit,
|
|
417
|
+
defaultSalesUnitQuantity: Number.isFinite(uomFields.defaultSalesUnitQuantity)
|
|
418
|
+
? uomFields.defaultSalesUnitQuantity
|
|
419
|
+
: null,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
353
423
|
export function LineItemDialog({
|
|
354
424
|
open,
|
|
355
425
|
kind,
|
|
@@ -382,6 +452,8 @@ export function LineItemDialog({
|
|
|
382
452
|
const [taxRates, setTaxRates] = React.useState<TaxRateOption[]>([]);
|
|
383
453
|
const [lineStatuses, setLineStatuses] = React.useState<StatusOption[]>([]);
|
|
384
454
|
const [unitOptions, setUnitOptions] = React.useState<UnitOption[]>([]);
|
|
455
|
+
const [deletedCatalogReference, setDeletedCatalogReference] =
|
|
456
|
+
React.useState(false);
|
|
385
457
|
const [, setLineStatusLoading] = React.useState(false);
|
|
386
458
|
const productOptionsRef = React.useRef<Map<string, ProductOption>>(new Map());
|
|
387
459
|
const variantOptionsRef = React.useRef<Map<string, VariantOption>>(new Map());
|
|
@@ -477,6 +549,7 @@ export function LineItemDialog({
|
|
|
477
549
|
setVariantOption(null);
|
|
478
550
|
setPriceOptions([]);
|
|
479
551
|
setUnitOptions([]);
|
|
552
|
+
setDeletedCatalogReference(false);
|
|
480
553
|
setEditingId(null);
|
|
481
554
|
setFormResetKey((prev) => prev + 1);
|
|
482
555
|
},
|
|
@@ -539,6 +612,29 @@ export function LineItemDialog({
|
|
|
539
612
|
}
|
|
540
613
|
}, []);
|
|
541
614
|
|
|
615
|
+
const loadProductOptionById = React.useCallback(
|
|
616
|
+
async (productId: string): Promise<ProductOption | null> => {
|
|
617
|
+
if (!productId) return null;
|
|
618
|
+
const response = await apiCall<{
|
|
619
|
+
items?: Array<Record<string, unknown>>;
|
|
620
|
+
}>(
|
|
621
|
+
`/api/catalog/products?id=${encodeURIComponent(productId)}&pageSize=1`,
|
|
622
|
+
undefined,
|
|
623
|
+
{ fallback: { items: [] } },
|
|
624
|
+
);
|
|
625
|
+
const items = Array.isArray(response.result?.items)
|
|
626
|
+
? response.result.items
|
|
627
|
+
: [];
|
|
628
|
+
const matched =
|
|
629
|
+
items.find((entry) => entry.id === productId) ?? items[0] ?? null;
|
|
630
|
+
if (!matched) return null;
|
|
631
|
+
const option = mapProductOption(matched);
|
|
632
|
+
if (option) productOptionsRef.current.set(option.id, option);
|
|
633
|
+
return option;
|
|
634
|
+
},
|
|
635
|
+
[],
|
|
636
|
+
);
|
|
637
|
+
|
|
542
638
|
const loadProductOptions = React.useCallback(
|
|
543
639
|
async (query?: string): Promise<LookupSelectItem[]> => {
|
|
544
640
|
const params = new URLSearchParams({ pageSize: "8" });
|
|
@@ -557,88 +653,22 @@ export function LineItemDialog({
|
|
|
557
653
|
: [];
|
|
558
654
|
const mapped = items
|
|
559
655
|
.map((item) => {
|
|
560
|
-
const
|
|
561
|
-
if (!
|
|
562
|
-
const productItem = item as ApiProductItem;
|
|
563
|
-
const title =
|
|
564
|
-
typeof item.title === "string"
|
|
565
|
-
? item.title
|
|
566
|
-
: typeof productItem.name === "string"
|
|
567
|
-
? productItem.name
|
|
568
|
-
: id;
|
|
569
|
-
const sku =
|
|
570
|
-
typeof productItem.sku === "string" ? productItem.sku : null;
|
|
571
|
-
const thumbnail =
|
|
572
|
-
typeof productItem.default_media_url === "string"
|
|
573
|
-
? productItem.default_media_url
|
|
574
|
-
: typeof productItem.defaultMediaUrl === "string"
|
|
575
|
-
? productItem.defaultMediaUrl
|
|
576
|
-
: null;
|
|
577
|
-
const pricing =
|
|
578
|
-
typeof productItem.pricing === "object" && productItem.pricing
|
|
579
|
-
? productItem.pricing
|
|
580
|
-
: null;
|
|
581
|
-
const metadata =
|
|
582
|
-
typeof productItem.metadata === "object" && productItem.metadata
|
|
583
|
-
? productItem.metadata
|
|
584
|
-
: null;
|
|
585
|
-
const pricingMeta = pricing as ApiPricingMetadata | null;
|
|
586
|
-
const metaMeta = metadata as ApiPricingMetadata | null;
|
|
587
|
-
const pricingTaxRateId =
|
|
588
|
-
typeof pricingMeta?.tax_rate_id === "string" &&
|
|
589
|
-
pricingMeta.tax_rate_id.trim().length
|
|
590
|
-
? pricingMeta.tax_rate_id.trim()
|
|
591
|
-
: typeof pricingMeta?.taxRateId === "string" &&
|
|
592
|
-
pricingMeta.taxRateId.trim().length
|
|
593
|
-
? pricingMeta.taxRateId.trim()
|
|
594
|
-
: null;
|
|
595
|
-
const metaTaxRateId =
|
|
596
|
-
typeof metaMeta?.taxRateId === "string" &&
|
|
597
|
-
metaMeta.taxRateId.trim().length
|
|
598
|
-
? metaMeta.taxRateId.trim()
|
|
599
|
-
: typeof metaMeta?.tax_rate_id === "string" &&
|
|
600
|
-
metaMeta.tax_rate_id.trim().length
|
|
601
|
-
? metaMeta.tax_rate_id.trim()
|
|
602
|
-
: null;
|
|
603
|
-
const taxRateValue = normalizeNumber(
|
|
604
|
-
pricingMeta?.tax_rate ??
|
|
605
|
-
pricingMeta?.taxRate ??
|
|
606
|
-
productItem.tax_rate ??
|
|
607
|
-
productItem.taxRate,
|
|
608
|
-
Number.NaN,
|
|
609
|
-
);
|
|
610
|
-
const uomFields = getUomProductFields(item);
|
|
611
|
-
const defaultUnit = uomFields.defaultUnit;
|
|
612
|
-
const defaultSalesUnit = uomFields.defaultSalesUnit;
|
|
613
|
-
const defaultSalesUnitQuantity = uomFields.defaultSalesUnitQuantity;
|
|
656
|
+
const option = mapProductOption(item);
|
|
657
|
+
if (!option) return null;
|
|
614
658
|
return {
|
|
615
|
-
id,
|
|
616
|
-
title,
|
|
617
|
-
subtitle: sku ?? undefined,
|
|
618
|
-
icon:
|
|
659
|
+
id: option.id,
|
|
660
|
+
title: option.title,
|
|
661
|
+
subtitle: option.sku ?? undefined,
|
|
662
|
+
icon: option.thumbnailUrl ? (
|
|
619
663
|
<img
|
|
620
|
-
src={
|
|
621
|
-
alt={title}
|
|
664
|
+
src={option.thumbnailUrl}
|
|
665
|
+
alt={option.title}
|
|
622
666
|
className="h-8 w-8 rounded object-cover"
|
|
623
667
|
/>
|
|
624
668
|
) : (
|
|
625
|
-
buildPlaceholder(title)
|
|
669
|
+
buildPlaceholder(option.title)
|
|
626
670
|
),
|
|
627
|
-
option
|
|
628
|
-
id,
|
|
629
|
-
title,
|
|
630
|
-
sku,
|
|
631
|
-
thumbnailUrl: thumbnail,
|
|
632
|
-
taxRateId: pricingTaxRateId ?? metaTaxRateId ?? null,
|
|
633
|
-
taxRate: Number.isFinite(taxRateValue) ? taxRateValue : null,
|
|
634
|
-
defaultUnit,
|
|
635
|
-
defaultSalesUnit,
|
|
636
|
-
defaultSalesUnitQuantity: Number.isFinite(
|
|
637
|
-
defaultSalesUnitQuantity,
|
|
638
|
-
)
|
|
639
|
-
? defaultSalesUnitQuantity
|
|
640
|
-
: null,
|
|
641
|
-
} satisfies ProductOption,
|
|
671
|
+
option,
|
|
642
672
|
} as LookupSelectItem & { option: ProductOption };
|
|
643
673
|
})
|
|
644
674
|
.filter(
|
|
@@ -1474,6 +1504,18 @@ export function LineItemDialog({
|
|
|
1474
1504
|
setFormValue?.("catalogSnapshot", null);
|
|
1475
1505
|
setFormValue?.("quantityUnit", null);
|
|
1476
1506
|
} else {
|
|
1507
|
+
if (deletedCatalogReference) {
|
|
1508
|
+
setDeletedCatalogReference(false);
|
|
1509
|
+
setProductOption(null);
|
|
1510
|
+
setVariantOption(null);
|
|
1511
|
+
setPriceOptions([]);
|
|
1512
|
+
setUnitOptions([]);
|
|
1513
|
+
setFormValue?.("productId", null);
|
|
1514
|
+
setFormValue?.("variantId", null);
|
|
1515
|
+
setFormValue?.("priceId", null);
|
|
1516
|
+
setFormValue?.("catalogSnapshot", null);
|
|
1517
|
+
setFormValue?.("quantityUnit", null);
|
|
1518
|
+
}
|
|
1477
1519
|
setFormValue?.("unitPrice", "");
|
|
1478
1520
|
setFormValue?.("priceMode", "gross");
|
|
1479
1521
|
}
|
|
@@ -1508,6 +1550,35 @@ export function LineItemDialog({
|
|
|
1508
1550
|
);
|
|
1509
1551
|
},
|
|
1510
1552
|
} satisfies CrudField,
|
|
1553
|
+
...(deletedCatalogReference
|
|
1554
|
+
? [
|
|
1555
|
+
{
|
|
1556
|
+
id: "deletedCatalogReference",
|
|
1557
|
+
label: t(
|
|
1558
|
+
"sales.documents.items.deletedCatalogReference.label",
|
|
1559
|
+
"Deleted catalog item",
|
|
1560
|
+
),
|
|
1561
|
+
type: "custom",
|
|
1562
|
+
layout: "full",
|
|
1563
|
+
component: () => (
|
|
1564
|
+
<Alert status="information" style="lighter">
|
|
1565
|
+
<AlertTitle>
|
|
1566
|
+
{t(
|
|
1567
|
+
"sales.documents.items.deletedCatalogReference.title",
|
|
1568
|
+
"This catalog item was deleted",
|
|
1569
|
+
)}
|
|
1570
|
+
</AlertTitle>
|
|
1571
|
+
<AlertDescription>
|
|
1572
|
+
{t(
|
|
1573
|
+
"sales.documents.items.deletedCatalogReference.description",
|
|
1574
|
+
"The saved line data is kept and the line is shown as a custom line. Switch to Catalog item to replace it with an existing product.",
|
|
1575
|
+
)}
|
|
1576
|
+
</AlertDescription>
|
|
1577
|
+
</Alert>
|
|
1578
|
+
),
|
|
1579
|
+
} satisfies CrudField,
|
|
1580
|
+
]
|
|
1581
|
+
: []),
|
|
1511
1582
|
...(!isCustomLine
|
|
1512
1583
|
? [
|
|
1513
1584
|
{
|
|
@@ -1528,6 +1599,7 @@ export function LineItemDialog({
|
|
|
1528
1599
|
const selectedOption = next
|
|
1529
1600
|
? (productOptionsRef.current.get(next) ?? null)
|
|
1530
1601
|
: null;
|
|
1602
|
+
if (next) setDeletedCatalogReference(false);
|
|
1531
1603
|
setProductOption(selectedOption);
|
|
1532
1604
|
setVariantOption(null);
|
|
1533
1605
|
setPriceOptions([]);
|
|
@@ -2371,6 +2443,7 @@ export function LineItemDialog({
|
|
|
2371
2443
|
loadProductOptions,
|
|
2372
2444
|
loadVariantOptions,
|
|
2373
2445
|
fetchLineStatusItems,
|
|
2446
|
+
deletedCatalogReference,
|
|
2374
2447
|
priceLoading,
|
|
2375
2448
|
priceOptions,
|
|
2376
2449
|
productOption,
|
|
@@ -2403,6 +2476,8 @@ export function LineItemDialog({
|
|
|
2403
2476
|
resetForm();
|
|
2404
2477
|
return;
|
|
2405
2478
|
}
|
|
2479
|
+
let cancelled = false;
|
|
2480
|
+
setDeletedCatalogReference(false);
|
|
2406
2481
|
setEditingId(initialLine.id);
|
|
2407
2482
|
const nextForm = defaultForm(initialLine.currencyCode ?? currencyCode);
|
|
2408
2483
|
const meta = initialLine.metadata ?? {};
|
|
@@ -2615,18 +2690,74 @@ export function LineItemDialog({
|
|
|
2615
2690
|
variantOptionsRef.current.set(initialLine.productVariantId, option);
|
|
2616
2691
|
resolvedVariantOption = option;
|
|
2617
2692
|
}
|
|
2618
|
-
if (resolvedProductOption)
|
|
2619
|
-
|
|
2693
|
+
if (resolvedProductOption) {
|
|
2694
|
+
setProductOption(resolvedProductOption);
|
|
2695
|
+
} else {
|
|
2696
|
+
setProductOption(null);
|
|
2697
|
+
}
|
|
2698
|
+
if (resolvedVariantOption) {
|
|
2699
|
+
setVariantOption(resolvedVariantOption);
|
|
2700
|
+
} else {
|
|
2701
|
+
setVariantOption(null);
|
|
2702
|
+
}
|
|
2620
2703
|
const customValues = extractCustomFieldValues(
|
|
2621
2704
|
initialLine as Record<string, unknown>,
|
|
2622
2705
|
);
|
|
2623
2706
|
const merged = { ...nextForm, ...customValues };
|
|
2707
|
+
const markDeletedCatalogReference = () => {
|
|
2708
|
+
if (cancelled) return;
|
|
2709
|
+
const nextMerged = { ...merged, lineMode: "custom" as const };
|
|
2710
|
+
setDeletedCatalogReference(true);
|
|
2711
|
+
setProductOption(null);
|
|
2712
|
+
setVariantOption(null);
|
|
2713
|
+
setPriceOptions([]);
|
|
2714
|
+
setUnitOptions([]);
|
|
2715
|
+
setInitialValues(nextMerged);
|
|
2716
|
+
setLineMode("custom");
|
|
2717
|
+
setFormResetKey((prev) => prev + 1);
|
|
2718
|
+
};
|
|
2624
2719
|
setInitialValues(merged);
|
|
2625
2720
|
setLineMode(merged.lineMode);
|
|
2626
2721
|
setFormResetKey((prev) => prev + 1);
|
|
2627
2722
|
if (initialLine.productId) {
|
|
2723
|
+
void (async () => {
|
|
2724
|
+
try {
|
|
2725
|
+
const currentProduct = await loadProductOptionById(
|
|
2726
|
+
initialLine.productId as string,
|
|
2727
|
+
);
|
|
2728
|
+
if (cancelled) return;
|
|
2729
|
+
if (!currentProduct) {
|
|
2730
|
+
markDeletedCatalogReference();
|
|
2731
|
+
return;
|
|
2732
|
+
}
|
|
2733
|
+
if (merged.lineMode !== "custom") {
|
|
2734
|
+
setProductOption(currentProduct);
|
|
2735
|
+
}
|
|
2736
|
+
if (initialLine.productVariantId) {
|
|
2737
|
+
await loadVariantOptions(
|
|
2738
|
+
initialLine.productId as string,
|
|
2739
|
+
currentProduct.thumbnailUrl,
|
|
2740
|
+
);
|
|
2741
|
+
if (cancelled) return;
|
|
2742
|
+
const currentVariant =
|
|
2743
|
+
variantOptionsRef.current.get(initialLine.productVariantId) ??
|
|
2744
|
+
null;
|
|
2745
|
+
if (!currentVariant) {
|
|
2746
|
+
markDeletedCatalogReference();
|
|
2747
|
+
return;
|
|
2748
|
+
}
|
|
2749
|
+
if (merged.lineMode !== "custom") {
|
|
2750
|
+
setVariantOption(currentVariant);
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
setDeletedCatalogReference(false);
|
|
2754
|
+
} catch (err) {
|
|
2755
|
+
console.error("sales.document.items.verifyCatalogReference", err);
|
|
2756
|
+
}
|
|
2757
|
+
})();
|
|
2628
2758
|
void loadProductUnits(initialLine.productId, resolvedProductOption).then(
|
|
2629
2759
|
(options) => {
|
|
2760
|
+
if (cancelled) return;
|
|
2630
2761
|
const requestedUnit = normalizeUnitCode(nextForm.quantityUnit);
|
|
2631
2762
|
if (
|
|
2632
2763
|
requestedUnit &&
|
|
@@ -2649,12 +2780,17 @@ export function LineItemDialog({
|
|
|
2649
2780
|
setPriceOptions([]);
|
|
2650
2781
|
setUnitOptions([]);
|
|
2651
2782
|
}
|
|
2783
|
+
return () => {
|
|
2784
|
+
cancelled = true;
|
|
2785
|
+
};
|
|
2652
2786
|
}, [
|
|
2653
2787
|
currencyCode,
|
|
2654
2788
|
findTaxRateIdByValue,
|
|
2655
2789
|
initialLine,
|
|
2790
|
+
loadProductOptionById,
|
|
2656
2791
|
loadPrices,
|
|
2657
2792
|
loadProductUnits,
|
|
2793
|
+
loadVariantOptions,
|
|
2658
2794
|
open,
|
|
2659
2795
|
resetForm,
|
|
2660
2796
|
]);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeCustomFieldResponse } from '@open-mercato/shared/lib/custom-fields/normalize'
|
|
2
|
+
import { extractCustomFieldEntries } from '@open-mercato/shared/lib/crud/custom-fields-client'
|
|
2
3
|
|
|
3
4
|
export const normalizeCustomFieldSubmitValue = (value: unknown): unknown => {
|
|
4
5
|
if (Array.isArray(value)) return value.filter((entry) => entry !== undefined)
|
|
@@ -24,47 +25,7 @@ export const prefixCustomFieldValues = (input?: Record<string, unknown> | null):
|
|
|
24
25
|
|
|
25
26
|
export const extractCustomFieldValues = (source?: Record<string, unknown> | null): Record<string, unknown> => {
|
|
26
27
|
if (!source || typeof source !== 'object') return {}
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
const trimmed = key.trim()
|
|
31
|
-
if (!trimmed.length) return
|
|
32
|
-
const normalizedKey = trimmed.startsWith('cf_')
|
|
33
|
-
? trimmed
|
|
34
|
-
: trimmed.startsWith('cf:')
|
|
35
|
-
? `cf_${trimmed.slice(3)}`
|
|
36
|
-
: `cf_${trimmed}`
|
|
37
|
-
if (normalizedKey.endsWith('__is_multi')) return
|
|
38
|
-
merged[normalizedKey] = value
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
Object.entries(source).forEach(([key, value]) => {
|
|
42
|
-
if (key.startsWith('cf_') || key.startsWith('cf:')) {
|
|
43
|
-
assign(key, value)
|
|
44
|
-
}
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
const customValues = (source as any).customValues ?? (source as any).custom_values
|
|
48
|
-
if (customValues && typeof customValues === 'object' && !Array.isArray(customValues)) {
|
|
49
|
-
Object.entries(customValues as Record<string, unknown>).forEach(([key, value]) => assign(key, value))
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const customFields = (source as any).customFields ?? (source as any).custom_fields
|
|
53
|
-
if (Array.isArray(customFields)) {
|
|
54
|
-
;(customFields as Array<Record<string, unknown>>).forEach((entry) => {
|
|
55
|
-
const key =
|
|
56
|
-
typeof entry?.key === 'string'
|
|
57
|
-
? entry.key
|
|
58
|
-
: typeof (entry as any)?.id === 'string'
|
|
59
|
-
? (entry as any).id
|
|
60
|
-
: null
|
|
61
|
-
if (!key) return
|
|
62
|
-
assign(key, (entry as any)?.value)
|
|
63
|
-
})
|
|
64
|
-
} else if (customFields && typeof customFields === 'object') {
|
|
65
|
-
Object.entries(customFields as Record<string, unknown>).forEach(([key, value]) => assign(key, value))
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const normalized = normalizeCustomFieldResponse(merged)
|
|
69
|
-
return normalized ? prefixCustomFieldValues(normalized) : prefixCustomFieldValues(merged)
|
|
28
|
+
const extracted = extractCustomFieldEntries(source)
|
|
29
|
+
const normalized = normalizeCustomFieldResponse(extracted)
|
|
30
|
+
return normalized ? prefixCustomFieldValues(normalized) : prefixCustomFieldValues(extracted)
|
|
70
31
|
}
|
|
@@ -925,6 +925,9 @@
|
|
|
925
925
|
"sales.documents.items.created": "Position hinzugefügt.",
|
|
926
926
|
"sales.documents.items.deleteConfirm": "Diese Position löschen?",
|
|
927
927
|
"sales.documents.items.deleted": "Position entfernt.",
|
|
928
|
+
"sales.documents.items.deletedCatalogReference.description": "Die gespeicherten Positionsdaten bleiben erhalten und die Position wird als benutzerdefinierte Position angezeigt. Wechsle zu Katalogprodukt, um sie durch ein vorhandenes Produkt zu ersetzen.",
|
|
929
|
+
"sales.documents.items.deletedCatalogReference.label": "Gelöschtes Katalogprodukt",
|
|
930
|
+
"sales.documents.items.deletedCatalogReference.title": "Dieses Katalogprodukt wurde gelöscht",
|
|
928
931
|
"sales.documents.items.editTitle": "Position bearbeiten",
|
|
929
932
|
"sales.documents.items.empty": "Noch keine Positionen.",
|
|
930
933
|
"sales.documents.items.errorCurrency": "Währung ist erforderlich.",
|
|
@@ -925,6 +925,9 @@
|
|
|
925
925
|
"sales.documents.items.created": "Line added.",
|
|
926
926
|
"sales.documents.items.deleteConfirm": "Delete this line item?",
|
|
927
927
|
"sales.documents.items.deleted": "Line removed.",
|
|
928
|
+
"sales.documents.items.deletedCatalogReference.description": "The saved line data is kept and the line is shown as a custom line. Switch to Catalog item to replace it with an existing product.",
|
|
929
|
+
"sales.documents.items.deletedCatalogReference.label": "Deleted catalog item",
|
|
930
|
+
"sales.documents.items.deletedCatalogReference.title": "This catalog item was deleted",
|
|
928
931
|
"sales.documents.items.editTitle": "Edit line",
|
|
929
932
|
"sales.documents.items.empty": "No items yet.",
|
|
930
933
|
"sales.documents.items.errorCurrency": "Currency is required.",
|
|
@@ -925,6 +925,9 @@
|
|
|
925
925
|
"sales.documents.items.created": "Línea agregada.",
|
|
926
926
|
"sales.documents.items.deleteConfirm": "¿Eliminar esta línea de artículo?",
|
|
927
927
|
"sales.documents.items.deleted": "Línea eliminada.",
|
|
928
|
+
"sales.documents.items.deletedCatalogReference.description": "Los datos guardados de la línea se conservan y la línea se muestra como personalizada. Cambia a Artículo del catálogo para reemplazarla por un producto existente.",
|
|
929
|
+
"sales.documents.items.deletedCatalogReference.label": "Artículo de catálogo eliminado",
|
|
930
|
+
"sales.documents.items.deletedCatalogReference.title": "Este artículo del catálogo fue eliminado",
|
|
928
931
|
"sales.documents.items.editTitle": "Editar línea",
|
|
929
932
|
"sales.documents.items.empty": "Aún no hay artículos.",
|
|
930
933
|
"sales.documents.items.errorCurrency": "La moneda es obligatoria.",
|
|
@@ -925,6 +925,9 @@
|
|
|
925
925
|
"sales.documents.items.created": "Pozycja dodana.",
|
|
926
926
|
"sales.documents.items.deleteConfirm": "Usunąć tę pozycję?",
|
|
927
927
|
"sales.documents.items.deleted": "Pozycja usunięta.",
|
|
928
|
+
"sales.documents.items.deletedCatalogReference.description": "Zapisane dane pozycji pozostają bez zmian, a pozycja jest pokazana jako niestandardowa. Przełącz na Produkt z katalogu, aby zastąpić ją istniejącym produktem.",
|
|
929
|
+
"sales.documents.items.deletedCatalogReference.label": "Usunięty produkt katalogowy",
|
|
930
|
+
"sales.documents.items.deletedCatalogReference.title": "Ten produkt katalogowy został usunięty",
|
|
928
931
|
"sales.documents.items.editTitle": "Edytuj pozycję",
|
|
929
932
|
"sales.documents.items.empty": "Brak pozycji.",
|
|
930
933
|
"sales.documents.items.errorCurrency": "Waluta jest wymagana.",
|