Introduction β
The publication collect is a feature that allows a user from an account to publish an item on a shared table of another account. A common case is when a supplier publish an item to the distributor.
Use cases β
- π Create one suggestion per target screen of the
exchange-receive-itemstask to allow teams to work independently and in parallel on only the data they are responsible for π΄ - π Enable any task to be called from the
exchange-send-itemstask, so that data can be transformed before being collected. π΄ - π Allow a value to be deleted from the collected data to keep only useful data π΄
- π Apply suggestions from a list of items to reduce multi-product manual collection effort π΄
- π Measure the time between a partner sending a suggestion and its application to their account, to gauge the efficiency of their process and the added value of the functionality. π
- π Allow suggestions to be filtered on a list of items according to the issuing partner, so that suggestions from trusted partners can be applied automatically π
- π Apply only certain suggestion fields to a list of items, to speed up the collection of the same field across multiple items π
- π Allow a suggestion not to be created if there is no difference with the previous one, to reduce the load on distributor teams π‘
- π Enable the creation of internal suggestions so that teams can validate data pushed by users and other systems. Suggestion is not necessary from the grid nor a publication π’
Entities β
// Publication entity
type Publication = {
id: number,
fromItemId: number, // Ref to item entity
partitionId: number, // Ref to partition entity
fromAccountId: number, // Ref to account entity
toAccountId: number, // Ref to account entity
authorId: number, // Ref to user entity
fields: Record<string, unknown>, // The item fields at the moment of the publication
fieldsMetadata: Record<string, ItemFieldMetadata>, // The item fields metadata at the moment of the publication
status: 'SENT' | 'RECEIVED' | 'REJECTED',
screenId: number,
createdAt: Date,
updatedAt: Date
};2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Suggestion entity
type Suggestion = {
id: number,
publicationId?: number, // Ref to publication entity
toItemId: number, // Ref to screen entity
fields: Record<string, unknown>, // The item fields at the moment of the suggestion creation
fieldsMetadata: Record<string, ItemFieldMetadata>, // The item fields metadata at the moment of the suggestion creation
deletedFields: string[], // The list of deleted fields (props delete=true in Suggestions XML) #use-case-3
isAcknowledged: boolean,
emitterName: string,
hasCreatedItem: boolean,
screenId: number, // Ref to the screen entity
createdAt: Date,
updatedAt: Date
};2
3
4
5
6
7
8
9
10
11
12
13
14
15
type ItemLink = {
id: number;
fromItemId: number;
/**
* As a reminder, we store lowerItemId / higherItemId to make the creation of an unique
* index (lowerItemId, higherItemId) easier and to avoid duplicate
*/
lowerItemId: number;
higherItemId: number;
status: 'ACTIVE' | 'ARCHIVED';
createdAt: Date;
updatedAt: Date;
}2
3
4
5
6
7
8
9
10
11
12
13
erDiagram
ItemPublication {
number id
json fields
json fieldsMetadata
json updated_fields
string status
date sent_at
date created_at
date updated_at
}
ItemSuggestion {
number id
json fields
json fieldsMetadata
json updated_fields
json deleted_fields
bool is_acknowledged
string emitter_name
date created_at
date updated_at
}
ItemLink {
number id
number from_item_id
number lower_item_id
number higher_item_id
string status
date created_at
date updated_at
}
Screen {}
User {}
Account {}
Item {}
SuggestionDeletedFields {}
ItemPublication ||--|{ Screen : screen_id
ItemPublication ||--|{ Account : from_account_id
ItemPublication ||--|{ Account : target_account_id
ItemPublication ||--|{ User : author_id
ItemPublication ||--|{ Item : item_id
ItemSuggestion ||--|{ Screen : screen_id
ItemSuggestion ||--|o ItemPublication : publication_id
ItemSuggestion ||--|{ Item : item_id
ItemLink |o--o| Item : from_item_id
ItemLink |o--o| Item : lower_item_id
ItemLink |o--o| Item : higher_item_id
ItemSuggestion }|--|{ SuggestionDeletedFields : deleted_fieldsAPI Usage β
We will use Calcifer API to cut the publication / collect process in smalls reusable API calls.
- Publish items
- Start a specific job
- Receive publications
- Create / Reuse item links
- Transform publications
- Collect publications
Publication β
Allow to publish a list of items to an account. Will create a publication for an item. We should later be able to batch it to publish multiple items in one call. Example: POST /v1/publication.
| Property | Description | Required | Example |
|---|---|---|---|
itemId | string | Yes | "1" |
screenId | string | Yes | "1" |
targetAccountId | string. Can be null when we target our own table in the case of internal suggestion #use-case-9 | "1" |
Output created publication.
Update publication status β
Allow to update the publication status. The purpose is to update the status of the publication to RECEIVED or ERROR. The route can only be called from a receiver account (a user from target_account_id) Example: PATCH /v1/publication/{publication.id}.
| Property | Description | Required | Example |
|---|---|---|---|
status | RECEIVED or ERROR. the status to update to | Yes | RECEIVED |
error | string. An optional error message |
Trigger external job β
Emit an event when the publication is done and react to it with a collect job.
Receive publication β
Allow to receive the publications in a JSON format. The purpose is to make any transformations to the JSON before collecting the publications. Example: POST /v1/publications/receive. By default, retrieve all publications not yet RECEIVED for the user account.
| Property | Description | Required | Example |
|---|---|---|---|
publicationId | string[]. The publication ids we want to receive | ["1", "2"] | |
accountId | string. The emitter account id we want to receive from | "1" |
Output a list of publications in a JSON format:
[
{
"id": "string",
"itemId": "string",
"fromAccountId": "string",
"authorId": "string",
"createdAt": "Date",
"updatedAt": "Date",
"screenId": "string",
"fields": "ItemField[]"
}
]2
3
4
5
6
7
8
9
10
11
12
ItemField:
type: object
properties:
id:
type: string
type:
type: string
enum:
- "IDENTIFIER"
- "CLASSIFICATION"
- "SINGLE_LINE_TEXT"
- "LONG_TEXT"
- "NUMBER"
- "DATE"
- "DATE_TIME"
- "HTML_TEXT"
- "IMAGE"
- "ATTACHMENT"
- "SINGLE_SELECT"
- "MULTIPLE_SELECT"
- "MULTIPLE_SELECT_QUANTIFIED"
- "MULTIPLE_SELECT_QUANTIFIED_WITH_COMMENTS"
- "MULTIPLE_SELECT_WITH_COMMENTS"
# - "CONDITIONAL_FORMATTING"
value:
description: "The value of the field. The type of the value depends on the type of the field."
type: object
additionalProperties:
anyOf:
- $ref: '#/components/schemas/ItemFieldValueString'
- $ref: '#/components/schemas/ItemFieldValueNumber'
- $ref: '#/components/schemas/ItemFieldValueStringArray'
- $ref: '#/components/schemas/ItemFieldValueMultipleSelectWithCommentAndQuantity'
- $ref: '#/components/schemas/ItemFieldValueFile'
ItemFieldValueString:
type: object
properties:
value:
type: string
ItemFieldValueNumber:
type: object
properties:
value:
type: number
suffix:
anyOf:
- type: string
- type: 'null'
ItemFieldValueStringArray:
type: object
properties:
value:
type: array
items:
type: string
ItemFieldValueMultipleSelectWithCommentAndQuantity:
type: object
properties:
value:
type: array
items:
type: object
properties:
id:
type: string
quantity:
anyOf:
- type: number
- type: 'null'
comment:
anyOf:
- type: string
- type: 'null'
ItemFieldValueFile:
type: object
properties:
id:
type: string
filename:
type: string
url:
type: string2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
Item link β
Describe the item_link entity used to link item between accounts. We can explicitly create / destroy a link between items.
Create link between items β
Allow to create a link between items. The purpose is to make sure that the item on one side is linked to an item on the other side. Example: POST /v1/item_link.
| Property | Description | Required | Example |
|---|---|---|---|
fromItemId | string | Yes | "1" |
toItemId | string. | Yes | "1" |
Output created item link.
{
"id": "string",
"fromItemId": "string",
"toItemId": "string",
"createdAt": "Date",
"updatedAt": "Date"
}2
3
4
5
6
7
In database, we use lowerItemId and higherItemId to create a unique index to avoid duplicate links.
WARNING
Only one link can exist between two items but an item can be linked to multiple items so be sure to check if a link already exists before creating a new one.
An activity log is emitted to trace the link creation.
Find link between items β
Allow to find a link between items. We find a link between an item id and an item on an account different from the item's one. If the distributor is finding a link, he will send the itemId found in the publication and the distributor accountId. Example: POST /v1/item_link/find.
| Property | Description | Required | Example |
|---|---|---|---|
fromItemId | string | Yes | "1" |
toItemId | string. | Yes | "1" |
Destroy link between items β
Allow to destroy a link between items. Example: DELETE /v1/item_link/{item_link.id}.
Suggestion API β
Allow a CRUD on suggestions.
TIP
To visualize the OpenAPI specification, you can use the Swagger Editor.
openapi: "3.0.2"
info:
title: Data Factory API
version: "1.0"
servers:
- url: "https://api.product-live.com"
components:
securitySchemes:
ApiKeyAuthHeader:
type: apiKey
in: header
name: X-API-KEY
ApiKeyAuthQuery:
type: apiKey
in: query
name: api_key
schemas:
# ItemField
BaseSuggestionDto:
type: object
properties:
fields:
type: object
publicationId:
type: string
itemId:
type: string
screenId:
type: string
deletedFields:
type: array
items:
type: string
source:
type: string
CreateSuggestionDto:
required:
- screenId
allOf:
- $ref: '#/components/schemas/BaseSuggestionDto'
UpdateSuggestionDto:
type: object
properties:
id:
type: string
isAcknowledged:
type: boolean
apply:
type: array
items:
type: string
example:
- "fieldId1"
- "fieldId2"
required:
- id
GetSuggestionDto:
allOf:
- type: object
properties:
id:
type: string
toItemId:
type: string
isAcknowledged:
type: boolean
object:
type: string
enum:
- "suggestion"
createdAt:
type: string
updatedAt:
type: string
- $ref: '#/components/schemas/BaseSuggestionDto'
ListSuggestionDto:
type: object
properties:
object:
type: string
enum:
- "list"
totalElements:
type: number
data:
type: array
items:
allOf:
- $ref: '#/components/schemas/GetSuggestionDto'
paths:
/v1/suggestions:
post:
description: Create a suggestion
security:
- ApiKeyAuthHeader: [ ]
- ApiKeyAuthQuery: [ ]
responses:
'200':
description: Suggestion successfully created
content:
application/json:
schema:
$ref: '#/components/schemas/CreateSuggestionDto'
get:
description: Get all suggestions available in an account
security:
- ApiKeyAuthHeader: []
- ApiKeyAuthQuery: []
responses:
'200':
description: Suggestions successfully retrieved
content:
application/json:
schema:
$ref: '#/components/schemas/ListSuggestionDto'
/v1/suggestions/{suggestion-id}:
put:
description: Update a suggestion
parameters:
- name: suggestion-id
in: path
required: true
schema:
type: string
security:
- ApiKeyAuthHeader: []
- ApiKeyAuthQuery: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateSuggestionDto'
responses:
'201':
description: Suggestion updated successfully.
content:
application/json:
schema:
$ref: '#/components/schemas/GetSuggestionDto'
get:
description: Get a suggestion by its ID
parameters:
- name: suggestion-id
in: path
required: true
schema:
type: string
security:
- ApiKeyAuthHeader: []
- ApiKeyAuthQuery: []
responses:
'200':
description: Suggestion successfully retrieved
content:
application/json:
schema:
$ref: '#/components/schemas/GetSuggestionDto'2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
We should be able to filter on itemsId but the find is not generic yet.
Publication collect flow example β
Pseudo code implementation of the publication collect with the API.
Publisher side β
POST /v1/publication
{
"itemId": "1",
"screenId": "1",
"targetAccountId": "2"
}
# Output
var publication = {
"id": "1",
"itemId": "1",
"fromAccountId": "1",
"authorId": "1",
"createdAt": "Date",
"updatedAt": "Date",
"screenId": "1",
"fields": {
"ean": {
"id": "2",
"type": "IDENTIFIER",
"value": "123"
},
"title": {
"id": "1",
"type": "SINGLE_LINE_TEXT",
"value": "titre"
}
}
}
# React to an event distributor side or trigger a job
POST /v1/data_factory/job_executions
{
"jobId": "1"
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Receiver side β
# Retrieve the publications
var publications =
POST /v1/publications/receive
{
"accountId": "1"
}
foreach publications as publication
# Retrieve or create link
var link =
POST /v1/item_link/find
{
"itemId": publication.itemId,
"otherItemAccountId": myAccountId
}
if link is null
var myItem =
POST /v1/items/find
{
"ean": publication.fields.ean.value
}
if myItem is null
# Create item
myItem =
POST /v1/items
{
"ean": publication.fields.ean.value,
"title": publication.fields.title.value
}
end
POST /v1/item_link
{
"fromItemId": publication.itemId,
"toItemId": myItem.id
}
end
# Once we have a link we can transform the publication and create suggestions
var suggestionFields = publication.fields
suggestionFields.title.value = "Titre: " + publication.fields.title.value
var suggestion =
POST /v1/suggestions
{
"publicationId": publication.id,
"fields": suggestionFields,
"deletedFields": ["title2"],
"screenId": "5"
}
# Always apply deleted field
PATCH /v1/suggestions/{suggestion.id}
{
"apply": ["title2"]
}
end2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55