@open-mercato/core 0.7.1-develop.7123.1.25365e718d → 0.7.1-develop.7130.1.fef2396fd8
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/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js +5 -117
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +1 -3
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/commands/deals.js +33 -9
- package/dist/modules/customers/commands/deals.js.map +3 -3
- package/dist/modules/customers/components/AddressEditor.js +71 -1
- package/dist/modules/customers/components/AddressEditor.js.map +2 -2
- package/dist/modules/customers/utils/addressFormat.js +18 -2
- package/dist/modules/customers/utils/addressFormat.js.map +2 -2
- package/dist/modules/sales/components/documents/AddressesSection.js +17 -2
- package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
- package/dist/modules/sales/components/documents/SalesDocumentForm.js +7 -24
- package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
- package/dist/modules/sales/components/documents/normalizeAddressDraft.js +29 -0
- package/dist/modules/sales/components/documents/normalizeAddressDraft.js.map +7 -0
- package/package.json +7 -7
- package/src/modules/customers/backend/customers/deals/[id]/hooks/types.ts +0 -23
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.ts +11 -172
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +1 -1
- package/src/modules/customers/commands/deals.ts +84 -9
- package/src/modules/customers/components/AddressEditor.tsx +116 -1
- package/src/modules/customers/i18n/de.json +6 -0
- package/src/modules/customers/i18n/en.json +6 -0
- package/src/modules/customers/i18n/es.json +6 -0
- package/src/modules/customers/i18n/ko.json +6 -0
- package/src/modules/customers/i18n/pl.json +6 -0
- package/src/modules/customers/utils/addressFormat.tsx +59 -1
- package/src/modules/sales/components/documents/AddressesSection.tsx +48 -5
- package/src/modules/sales/components/documents/SalesDocumentForm.tsx +5 -23
- package/src/modules/sales/components/documents/normalizeAddressDraft.ts +29 -0
|
@@ -1867,11 +1867,17 @@
|
|
|
1867
1867
|
"customers.people.detail.addresses.fields.line1": "주소 1",
|
|
1868
1868
|
"customers.people.detail.addresses.fields.line2": "주소 2",
|
|
1869
1869
|
"customers.people.detail.addresses.fields.longitude": "경도",
|
|
1870
|
+
"customers.people.detail.addresses.fields.phone": "전화번호",
|
|
1870
1871
|
"customers.people.detail.addresses.fields.postalCode": "우편번호",
|
|
1871
1872
|
"customers.people.detail.addresses.fields.primary": "기본 주소로 설정",
|
|
1872
1873
|
"customers.people.detail.addresses.fields.region": "지역 / 주",
|
|
1873
1874
|
"customers.people.detail.addresses.fields.street": "도로명",
|
|
1874
1875
|
"customers.people.detail.addresses.fields.streetExtra": "추가 주소 정보",
|
|
1876
|
+
"customers.people.detail.addresses.fields.taxId": "세금 번호",
|
|
1877
|
+
"customers.people.detail.addresses.fields.taxId.euVat": "EU 부가세 번호",
|
|
1878
|
+
"customers.people.detail.addresses.fields.taxId.other": "세금 번호",
|
|
1879
|
+
"customers.people.detail.addresses.fields.taxId.plNip": "납세자 번호",
|
|
1880
|
+
"customers.people.detail.addresses.fields.taxIdType": "세금 번호 유형",
|
|
1875
1881
|
"customers.people.detail.addresses.fields.type": "주소 유형",
|
|
1876
1882
|
"customers.people.detail.addresses.formatLoadError": "주소 설정을 불러오지 못했습니다",
|
|
1877
1883
|
"customers.people.detail.addresses.formatLoading": "주소 설정을 불러오는 중…",
|
|
@@ -1867,11 +1867,17 @@
|
|
|
1867
1867
|
"customers.people.detail.addresses.fields.line1": "Adres (linia 1)",
|
|
1868
1868
|
"customers.people.detail.addresses.fields.line2": "Adres (linia 2)",
|
|
1869
1869
|
"customers.people.detail.addresses.fields.longitude": "Długość geograficzna",
|
|
1870
|
+
"customers.people.detail.addresses.fields.phone": "Telefon",
|
|
1870
1871
|
"customers.people.detail.addresses.fields.postalCode": "Kod pocztowy",
|
|
1871
1872
|
"customers.people.detail.addresses.fields.primary": "Ustaw jako główny",
|
|
1872
1873
|
"customers.people.detail.addresses.fields.region": "Region / województwo",
|
|
1873
1874
|
"customers.people.detail.addresses.fields.street": "Ulica",
|
|
1874
1875
|
"customers.people.detail.addresses.fields.streetExtra": "Dodatkowe informacje adresowe",
|
|
1876
|
+
"customers.people.detail.addresses.fields.taxId": "Nr podatkowy",
|
|
1877
|
+
"customers.people.detail.addresses.fields.taxId.euVat": "VAT UE",
|
|
1878
|
+
"customers.people.detail.addresses.fields.taxId.other": "Nr podatkowy",
|
|
1879
|
+
"customers.people.detail.addresses.fields.taxId.plNip": "NIP",
|
|
1880
|
+
"customers.people.detail.addresses.fields.taxIdType": "Typ numeru",
|
|
1875
1881
|
"customers.people.detail.addresses.fields.type": "Typ adresu",
|
|
1876
1882
|
"customers.people.detail.addresses.formatLoadError": "Nie udało się wczytać konfiguracji adresów",
|
|
1877
1883
|
"customers.people.detail.addresses.formatLoading": "Ładowanie preferencji adresowych…",
|
|
@@ -13,6 +13,34 @@ export type AddressValue = {
|
|
|
13
13
|
postalCode?: string | null
|
|
14
14
|
country?: string | null
|
|
15
15
|
companyName?: string | null
|
|
16
|
+
/**
|
|
17
|
+
* Contact details that belong to the ADDRESS rather than to the customer: who to call about this
|
|
18
|
+
* delivery, and the tax identifier this invoice address was billed under. They remain available to
|
|
19
|
+
* address editors and snapshot payloads, but are deliberately excluded from `formatAddressLines`
|
|
20
|
+
* and `AddressView`, whose existing contract remains postal-only.
|
|
21
|
+
*
|
|
22
|
+
* `taxIdType` interprets the value in Stripe's `{country}_{kind}` vocabulary (`pl_nip`, `eu_vat`,
|
|
23
|
+
* `other`, widened additively): `1234567890` and `PL1234567890` are the same business, and only the
|
|
24
|
+
* type tells a domestic identifier from an EU VAT number. It is metadata about `taxId`, never a
|
|
25
|
+
* displayed field of its own.
|
|
26
|
+
*/
|
|
27
|
+
phone?: string | null
|
|
28
|
+
taxId?: string | null
|
|
29
|
+
taxIdType?: string | null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Tax-id labels keyed by `taxIdType`, for a caller that wants the identifier named correctly rather
|
|
34
|
+
* than generically.
|
|
35
|
+
*
|
|
36
|
+
* The stored scheme is chosen explicitly rather than inferred from the identifier. `other` also
|
|
37
|
+
* covers an address written before `taxIdType` existed. An unrecognised type takes the `other` route
|
|
38
|
+
* instead of guessing a domestic scheme.
|
|
39
|
+
*/
|
|
40
|
+
export type TaxIdLabelByType = {
|
|
41
|
+
plNip: string
|
|
42
|
+
euVat: string
|
|
43
|
+
other: string
|
|
16
44
|
}
|
|
17
45
|
|
|
18
46
|
export type AddressJsonShape = {
|
|
@@ -100,6 +128,31 @@ export function formatAddressString(address: AddressValue, format: AddressFormat
|
|
|
100
128
|
return formatAddressLines(address, format).filter(Boolean).join(separator)
|
|
101
129
|
}
|
|
102
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Which member of a label map names which scheme. Private, and deliberately not exhaustive: an
|
|
133
|
+
* unrecognised type resolves to `other`, so the vocabulary can widen without every caller being
|
|
134
|
+
* updated in the same release.
|
|
135
|
+
*/
|
|
136
|
+
const TAX_ID_LABEL_KEY_BY_TYPE: Record<string, keyof TaxIdLabelByType> = {
|
|
137
|
+
pl_nip: 'plNip',
|
|
138
|
+
eu_vat: 'euVat',
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The label a tax identifier should carry, given its type. Exported because the editor renders the
|
|
143
|
+
* same identifier as an input and must name it the same way this formatter does — two copies of the
|
|
144
|
+
* mapping is exactly how a foreign number ends up under a domestic scheme's name.
|
|
145
|
+
*/
|
|
146
|
+
export function resolveTaxIdLabel(
|
|
147
|
+
label: string | TaxIdLabelByType | undefined,
|
|
148
|
+
taxIdType: string | null | undefined,
|
|
149
|
+
): string | undefined {
|
|
150
|
+
if (!label) return undefined
|
|
151
|
+
if (typeof label === 'string') return label
|
|
152
|
+
const key = TAX_ID_LABEL_KEY_BY_TYPE[typeof taxIdType === 'string' ? taxIdType : ''] ?? 'other'
|
|
153
|
+
return label[key]
|
|
154
|
+
}
|
|
155
|
+
|
|
103
156
|
type AddressViewProps = {
|
|
104
157
|
address: AddressValue
|
|
105
158
|
format: AddressFormatStrategy
|
|
@@ -107,7 +160,12 @@ type AddressViewProps = {
|
|
|
107
160
|
lineClassName?: string
|
|
108
161
|
}
|
|
109
162
|
|
|
110
|
-
export function AddressView({
|
|
163
|
+
export function AddressView({
|
|
164
|
+
address,
|
|
165
|
+
format,
|
|
166
|
+
className,
|
|
167
|
+
lineClassName,
|
|
168
|
+
}: AddressViewProps): React.ReactElement | null {
|
|
111
169
|
const lines = formatAddressLines(address, format)
|
|
112
170
|
if (!lines.length) return null
|
|
113
171
|
return (
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
|
|
3
1
|
"use client"
|
|
4
2
|
|
|
5
3
|
import * as React from 'react'
|
|
@@ -72,17 +70,47 @@ const emptyDraft: AddressEditorDraft = {
|
|
|
72
70
|
region: '',
|
|
73
71
|
postalCode: '',
|
|
74
72
|
country: '',
|
|
73
|
+
taxId: '',
|
|
74
|
+
taxIdType: '',
|
|
75
|
+
phone: '',
|
|
75
76
|
isPrimary: false,
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
const EDITABLE_SNAPSHOT_KEYS = new Set(Object.keys(emptyDraft))
|
|
79
80
|
|
|
81
|
+
/**
|
|
82
|
+
* The fields the editor owns, typed, plus whatever else the caller's snapshot carried.
|
|
83
|
+
*
|
|
84
|
+
* The index signature is what lets an integration's extra keys — anything a future
|
|
85
|
+
* integration writes — survive the merge-back below, while the named fields stay `string` for callers
|
|
86
|
+
* that read them. `taxId` and `phone` are NOT among them any more: the editor renders both, so they
|
|
87
|
+
* are assigned from the draft like every other field. Reading
|
|
88
|
+
* `normalized.city` off a bare `Record<string, unknown>` yields `unknown`, which is what forced the
|
|
89
|
+
* `@ts-nocheck` on this file.
|
|
90
|
+
*/
|
|
91
|
+
type NormalizedAddressDraft = {
|
|
92
|
+
name?: string
|
|
93
|
+
purpose?: string
|
|
94
|
+
companyName?: string
|
|
95
|
+
addressLine1?: string
|
|
96
|
+
addressLine2?: string
|
|
97
|
+
buildingNumber?: string
|
|
98
|
+
flatNumber?: string
|
|
99
|
+
city?: string
|
|
100
|
+
region?: string
|
|
101
|
+
postalCode?: string
|
|
102
|
+
country?: string
|
|
103
|
+
taxId?: string
|
|
104
|
+
phone?: string
|
|
105
|
+
isPrimary?: boolean
|
|
106
|
+
} & Record<string, unknown>
|
|
107
|
+
|
|
80
108
|
function normalizeAddressDraft(
|
|
81
109
|
draft?: AddressEditorDraft | null,
|
|
82
110
|
previous?: Record<string, unknown> | null,
|
|
83
|
-
):
|
|
111
|
+
): NormalizedAddressDraft | null {
|
|
84
112
|
if (!draft) return null
|
|
85
|
-
const normalized:
|
|
113
|
+
const normalized: NormalizedAddressDraft = {}
|
|
86
114
|
let hasEditableContent = false
|
|
87
115
|
const assign = (key: keyof AddressEditorDraft, target: string) => {
|
|
88
116
|
const value = draft[key]
|
|
@@ -103,6 +131,9 @@ function normalizeAddressDraft(
|
|
|
103
131
|
assign('region', 'region')
|
|
104
132
|
assign('postalCode', 'postalCode')
|
|
105
133
|
assign('country', 'country')
|
|
134
|
+
assign('taxId', 'taxId')
|
|
135
|
+
assign('taxIdType', 'taxIdType')
|
|
136
|
+
assign('phone', 'phone')
|
|
106
137
|
assign('isPrimary', 'isPrimary')
|
|
107
138
|
if (!hasEditableContent) return null
|
|
108
139
|
if (previous) {
|
|
@@ -129,6 +160,9 @@ function draftFromSnapshot(snapshot?: Record<string, unknown> | null): AddressEd
|
|
|
129
160
|
region: typeof record.region === 'string' ? record.region : '',
|
|
130
161
|
postalCode: typeof record.postalCode === 'string' ? record.postalCode : '',
|
|
131
162
|
country: typeof record.country === 'string' ? record.country : '',
|
|
163
|
+
taxId: typeof record.taxId === 'string' ? record.taxId : '',
|
|
164
|
+
taxIdType: typeof record.taxIdType === 'string' ? record.taxIdType : '',
|
|
165
|
+
phone: typeof record.phone === 'string' ? record.phone : '',
|
|
132
166
|
isPrimary: record.isPrimary === true,
|
|
133
167
|
}
|
|
134
168
|
}
|
|
@@ -370,7 +404,10 @@ export function SalesDocumentAddressesSection({
|
|
|
370
404
|
companyName: value.companyName ?? null,
|
|
371
405
|
}
|
|
372
406
|
})
|
|
373
|
-
.
|
|
407
|
+
// Narrow to non-null only. Asserting `DocumentAddressAssignment` here was wrong: its
|
|
408
|
+
// optional fields (`name?`, `purpose?`, …) admit `undefined`, which the mapped literal's
|
|
409
|
+
// `string | null` does not, so the predicate was not assignable to what it filtered.
|
|
410
|
+
.filter((entry): entry is NonNullable<typeof entry> => entry !== null)
|
|
374
411
|
setDocumentAddresses(mapped)
|
|
375
412
|
} else {
|
|
376
413
|
setDocumentAddresses([])
|
|
@@ -1093,9 +1130,12 @@ export function SalesDocumentAddressesSection({
|
|
|
1093
1130
|
<AddressEditor
|
|
1094
1131
|
value={shippingDraft}
|
|
1095
1132
|
format={addressFormat}
|
|
1133
|
+
showPhoneField
|
|
1134
|
+
showTaxIdField
|
|
1096
1135
|
t={t as Translator}
|
|
1097
1136
|
onChange={(next) => setShippingDraft(next)}
|
|
1098
1137
|
hidePrimaryToggle
|
|
1138
|
+
disabled={locked}
|
|
1099
1139
|
/>
|
|
1100
1140
|
<SwitchField
|
|
1101
1141
|
label={t('sales.documents.form.address.saveToCustomer', 'Save this address to the customer')}
|
|
@@ -1160,9 +1200,12 @@ export function SalesDocumentAddressesSection({
|
|
|
1160
1200
|
<AddressEditor
|
|
1161
1201
|
value={billingDraft}
|
|
1162
1202
|
format={addressFormat}
|
|
1203
|
+
showPhoneField
|
|
1204
|
+
showTaxIdField
|
|
1163
1205
|
t={t as Translator}
|
|
1164
1206
|
onChange={(next) => setBillingDraft(next)}
|
|
1165
1207
|
hidePrimaryToggle
|
|
1208
|
+
disabled={locked}
|
|
1166
1209
|
/>
|
|
1167
1210
|
<SwitchField
|
|
1168
1211
|
label={t('sales.documents.form.address.saveToCustomer', 'Save this address to the customer')}
|
|
@@ -60,6 +60,7 @@ import { AddressEditor, type AddressEditorDraft } from '@open-mercato/core/modul
|
|
|
60
60
|
import { useSalesChannelsEnabled } from '../useSalesChannelsEnabled'
|
|
61
61
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
62
62
|
import { SalesOrderDraftLines, createSalesOrderLineDraft, type SalesOrderLineDraft } from './SalesOrderDraftLines'
|
|
63
|
+
import { normalizeAddressDraft } from './normalizeAddressDraft'
|
|
63
64
|
|
|
64
65
|
const logger = createLogger('sales')
|
|
65
66
|
|
|
@@ -435,29 +436,6 @@ function parseCustomerOptions(items: unknown[], kind: 'person' | 'company'): Cus
|
|
|
435
436
|
return parsed
|
|
436
437
|
}
|
|
437
438
|
|
|
438
|
-
function normalizeAddressDraft(draft?: AddressDraft | null): Record<string, unknown> | null {
|
|
439
|
-
if (!draft) return null
|
|
440
|
-
const normalized: Record<string, unknown> = {}
|
|
441
|
-
const assign = (key: keyof AddressDraft, target: string) => {
|
|
442
|
-
const value = draft[key]
|
|
443
|
-
if (typeof value === 'string' && value.trim().length) normalized[target] = value.trim()
|
|
444
|
-
if (typeof value === 'boolean') normalized[target] = value
|
|
445
|
-
}
|
|
446
|
-
assign('name', 'name')
|
|
447
|
-
assign('purpose', 'purpose')
|
|
448
|
-
assign('companyName', 'companyName')
|
|
449
|
-
assign('addressLine1', 'addressLine1')
|
|
450
|
-
assign('addressLine2', 'addressLine2')
|
|
451
|
-
assign('buildingNumber', 'buildingNumber')
|
|
452
|
-
assign('flatNumber', 'flatNumber')
|
|
453
|
-
assign('city', 'city')
|
|
454
|
-
assign('region', 'region')
|
|
455
|
-
assign('postalCode', 'postalCode')
|
|
456
|
-
assign('country', 'country')
|
|
457
|
-
assign('isPrimary', 'isPrimary')
|
|
458
|
-
return Object.keys(normalized).length ? normalized : null
|
|
459
|
-
}
|
|
460
|
-
|
|
461
439
|
type DocumentNumberFieldProps = CrudCustomFieldRenderProps & { t: Translator }
|
|
462
440
|
|
|
463
441
|
type BillingAddressSectionFieldProps = CrudCustomFieldRenderProps & {
|
|
@@ -655,6 +633,8 @@ function BillingAddressSectionField({ values, setFormValue, t, addressesLoading,
|
|
|
655
633
|
t={t}
|
|
656
634
|
onChange={(next) => updateValue('billingAddressDraft', next)}
|
|
657
635
|
hidePrimaryToggle
|
|
636
|
+
showPhoneField
|
|
637
|
+
showTaxIdField
|
|
658
638
|
/>
|
|
659
639
|
<SwitchField
|
|
660
640
|
containerClassName="col-span-2"
|
|
@@ -1267,6 +1247,8 @@ export function SalesDocumentForm({ onCreated, isSubmitting = false, initialKind
|
|
|
1267
1247
|
t={t}
|
|
1268
1248
|
onChange={(next) => updateValue('shippingAddressDraft', next)}
|
|
1269
1249
|
hidePrimaryToggle
|
|
1250
|
+
showPhoneField
|
|
1251
|
+
showTaxIdField
|
|
1270
1252
|
/>
|
|
1271
1253
|
<SwitchField
|
|
1272
1254
|
containerClassName="col-span-2"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { AddressEditorDraft } from '@open-mercato/core/modules/customers/components/AddressEditor'
|
|
2
|
+
|
|
3
|
+
export function normalizeAddressDraft(
|
|
4
|
+
draft?: AddressEditorDraft | null,
|
|
5
|
+
): Record<string, unknown> | null {
|
|
6
|
+
if (!draft) return null
|
|
7
|
+
const normalized: Record<string, unknown> = {}
|
|
8
|
+
const assign = (key: keyof AddressEditorDraft, target: string) => {
|
|
9
|
+
const value = draft[key]
|
|
10
|
+
if (typeof value === 'string' && value.trim().length) normalized[target] = value.trim()
|
|
11
|
+
if (typeof value === 'boolean') normalized[target] = value
|
|
12
|
+
}
|
|
13
|
+
assign('name', 'name')
|
|
14
|
+
assign('purpose', 'purpose')
|
|
15
|
+
assign('companyName', 'companyName')
|
|
16
|
+
assign('addressLine1', 'addressLine1')
|
|
17
|
+
assign('addressLine2', 'addressLine2')
|
|
18
|
+
assign('buildingNumber', 'buildingNumber')
|
|
19
|
+
assign('flatNumber', 'flatNumber')
|
|
20
|
+
assign('city', 'city')
|
|
21
|
+
assign('region', 'region')
|
|
22
|
+
assign('postalCode', 'postalCode')
|
|
23
|
+
assign('country', 'country')
|
|
24
|
+
assign('taxId', 'taxId')
|
|
25
|
+
assign('taxIdType', 'taxIdType')
|
|
26
|
+
assign('phone', 'phone')
|
|
27
|
+
assign('isPrimary', 'isPrimary')
|
|
28
|
+
return Object.keys(normalized).length ? normalized : null
|
|
29
|
+
}
|