Skip to content

Commit bde20b2

Browse files
authored
feat(cart,order): add support for custom types and fields (#341)
- Add support for missing line-item custom field and type actions: - `setLineItemCustomField` on cart - `setLineItemCustomType` on cart - `setLineItemCustomField` on order - `setLineItemCustomType` on order - Fix: copy line item variant attributes on OrderImport
1 parent db94910 commit bde20b2

File tree

6 files changed

+571
-1
lines changed

6 files changed

+571
-1
lines changed

.changeset/brave-flies-try.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@labdigital/commercetools-mock": minor
3+
---
4+
5+
feat: implement support for set line-item custom types and fields on carts and orders

src/repositories/cart/actions.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import type {
2626
CartSetCustomTypeAction,
2727
CartSetCustomerEmailAction,
2828
CartSetDirectDiscountsAction,
29+
CartSetLineItemCustomFieldAction,
30+
CartSetLineItemCustomTypeAction,
2931
CartSetLineItemShippingDetailsAction,
3032
CartSetLocaleAction,
3133
CartSetShippingAddressAction,
@@ -662,6 +664,82 @@ export class CartUpdateHandler
662664
);
663665
}
664666

667+
setLineItemCustomField(
668+
context: RepositoryContext,
669+
resource: Writable<Cart>,
670+
{
671+
lineItemId,
672+
lineItemKey,
673+
name,
674+
value,
675+
action,
676+
}: CartSetLineItemCustomFieldAction,
677+
) {
678+
const lineItem = resource.lineItems.find(
679+
(x) =>
680+
(lineItemId && x.id === lineItemId) ||
681+
(lineItemKey && x.key === lineItemKey),
682+
);
683+
684+
if (!lineItem) {
685+
// Check if line item is found
686+
throw new CommercetoolsError<GeneralError>({
687+
code: "General",
688+
message: lineItemKey
689+
? `A line item with key '${lineItemKey}' not found.`
690+
: `A line item with ID '${lineItemId}' not found.`,
691+
});
692+
}
693+
694+
if (!lineItem.custom) {
695+
throw new Error("Resource has no custom field");
696+
}
697+
698+
lineItem.custom.fields[name] = value;
699+
}
700+
701+
setLineItemCustomType(
702+
context: RepositoryContext,
703+
resource: Writable<Cart>,
704+
{ lineItemId, lineItemKey, type, fields }: CartSetLineItemCustomTypeAction,
705+
) {
706+
const lineItem = resource.lineItems.find(
707+
(x) =>
708+
(lineItemId && x.id === lineItemId) ||
709+
(lineItemKey && x.key === lineItemKey),
710+
);
711+
712+
if (!lineItem) {
713+
// Check if line item is found
714+
throw new CommercetoolsError<GeneralError>({
715+
code: "General",
716+
message: lineItemKey
717+
? `A line item with key '${lineItemKey}' not found.`
718+
: `A line item with ID '${lineItemId}' not found.`,
719+
});
720+
}
721+
722+
if (!type) {
723+
lineItem.custom = undefined;
724+
} else {
725+
const resolvedType = this._storage.getByResourceIdentifier(
726+
context.projectKey,
727+
type,
728+
);
729+
if (!resolvedType) {
730+
throw new Error(`Type ${type} not found`);
731+
}
732+
733+
lineItem.custom = {
734+
type: {
735+
typeId: "type",
736+
id: resolvedType.id,
737+
},
738+
fields: fields || {},
739+
};
740+
}
741+
}
742+
665743
setLineItemShippingDetails(
666744
context: RepositoryContext,
667745
resource: Writable<Cart>,

src/repositories/order/actions.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type {
22
CustomLineItemReturnItem,
3+
GeneralError,
34
LineItemReturnItem,
45
Order,
56
OrderAddPaymentAction,
@@ -9,10 +10,13 @@ import type {
910
OrderChangeShipmentStateAction,
1011
OrderSetBillingAddressAction,
1112
OrderSetCustomFieldAction,
13+
OrderSetCustomLineItemCustomTypeAction,
1214
OrderSetCustomTypeAction,
1315
OrderSetCustomerEmailAction,
1416
OrderSetCustomerIdAction,
1517
OrderSetDeliveryCustomFieldAction,
18+
OrderSetLineItemCustomFieldAction,
19+
OrderSetLineItemCustomTypeAction,
1620
OrderSetLocaleAction,
1721
OrderSetOrderNumberAction,
1822
OrderSetParcelCustomFieldAction,
@@ -27,6 +31,7 @@ import type {
2731
Store,
2832
SyncInfo,
2933
} from "@commercetools/platform-sdk";
34+
import { CommercetoolsError } from "~src/exceptions";
3035
import { getBaseResourceProperties } from "~src/helpers";
3136
import type { Writable } from "~src/types";
3237
import type { RepositoryContext, UpdateHandlerInterface } from "../abstract";
@@ -205,6 +210,82 @@ export class OrderUpdateHandler
205210
}
206211
}
207212

213+
setLineItemCustomField(
214+
context: RepositoryContext,
215+
resource: Order,
216+
{
217+
lineItemId,
218+
lineItemKey,
219+
name,
220+
value,
221+
action,
222+
}: OrderSetLineItemCustomFieldAction,
223+
) {
224+
const lineItem = resource.lineItems.find(
225+
(x) =>
226+
(lineItemId && x.id === lineItemId) ||
227+
(lineItemKey && x.key === lineItemKey),
228+
);
229+
230+
if (!lineItem) {
231+
// Check if line item is found
232+
throw new CommercetoolsError<GeneralError>({
233+
code: "General",
234+
message: lineItemKey
235+
? `A line item with key '${lineItemKey}' not found.`
236+
: `A line item with ID '${lineItemId}' not found.`,
237+
});
238+
}
239+
240+
if (!lineItem.custom) {
241+
throw new Error("Resource has no custom field");
242+
}
243+
244+
lineItem.custom.fields[name] = value;
245+
}
246+
247+
setLineItemCustomType(
248+
context: RepositoryContext,
249+
resource: Writable<Order>,
250+
{ lineItemId, lineItemKey, type, fields }: OrderSetLineItemCustomTypeAction,
251+
) {
252+
const lineItem = resource.lineItems.find(
253+
(x) =>
254+
(lineItemId && x.id === lineItemId) ||
255+
(lineItemKey && x.key === lineItemKey),
256+
);
257+
258+
if (!lineItem) {
259+
// Check if line item is found
260+
throw new CommercetoolsError<GeneralError>({
261+
code: "General",
262+
message: lineItemKey
263+
? `A line item with key '${lineItemKey}' not found.`
264+
: `A line item with ID '${lineItemId}' not found.`,
265+
});
266+
}
267+
268+
if (!type) {
269+
lineItem.custom = undefined;
270+
} else {
271+
const resolvedType = this._storage.getByResourceIdentifier(
272+
context.projectKey,
273+
type,
274+
);
275+
if (!resolvedType) {
276+
throw new Error(`Type ${type} not found`);
277+
}
278+
279+
lineItem.custom = {
280+
type: {
281+
typeId: "type",
282+
id: resolvedType.id,
283+
},
284+
fields: fields || {},
285+
};
286+
}
287+
}
288+
208289
setLocale(
209290
context: RepositoryContext,
210291
resource: Writable<Order>,

src/repositories/order/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ export class OrderRepository extends AbstractResourceRepository<"order"> {
219219
id: variant.id,
220220
sku: variant.sku,
221221
price: createPrice(draft.price),
222+
attributes: variant.attributes,
222223
},
223224
};
224225

src/services/cart.test.ts

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,6 +711,176 @@ describe("Cart Update Actions", () => {
711711
]);
712712
});
713713

714+
test("setLineItemCustomField", async () => {
715+
const product = await supertest(ctMock.app)
716+
.post("/dummy/products")
717+
.send(productDraft)
718+
.then((x) => x.body);
719+
720+
assert(product, "product not created");
721+
722+
const type = await supertest(ctMock.app)
723+
.post("/dummy/types")
724+
.send({
725+
key: "my-type",
726+
name: {
727+
en: "My Type",
728+
},
729+
description: {
730+
en: "My Type Description",
731+
},
732+
fieldDefinitions: [
733+
{
734+
name: "foo",
735+
label: {
736+
en: "foo",
737+
},
738+
required: false,
739+
type: {
740+
name: "String",
741+
},
742+
inputHint: "SingleLine",
743+
},
744+
],
745+
})
746+
.then((x) => x.body);
747+
748+
assert(type, "type not created");
749+
750+
const myCart = await supertest(ctMock.app)
751+
.post("/dummy/carts")
752+
.send({
753+
currency: "EUR",
754+
lineItems: [
755+
{
756+
sku: product.masterData.current.masterVariant.sku,
757+
quantity: 1,
758+
custom: {
759+
type: {
760+
typeId: "type",
761+
key: "my-type",
762+
},
763+
fields: {},
764+
},
765+
},
766+
],
767+
})
768+
.then((x) => x.body);
769+
770+
const lineItem = myCart.lineItems[0];
771+
assert(lineItem, "lineItem not created");
772+
773+
const response = await supertest(ctMock.app)
774+
.post(`/dummy/carts/${myCart.id}`)
775+
.send({
776+
version: myCart.version,
777+
actions: [
778+
{
779+
action: "setLineItemCustomField",
780+
lineItemId: lineItem.id,
781+
name: "foo",
782+
value: "bar",
783+
},
784+
],
785+
});
786+
787+
expect(response.status).toBe(200);
788+
expect(response.body.version).toBe(2);
789+
expect(response.body.lineItems).toMatchObject([
790+
{
791+
id: lineItem.id,
792+
custom: {
793+
fields: {
794+
foo: "bar",
795+
},
796+
},
797+
},
798+
]);
799+
});
800+
801+
test("setLineItemCustomType", async () => {
802+
const product = await supertest(ctMock.app)
803+
.post("/dummy/products")
804+
.send(productDraft)
805+
.then((x) => x.body);
806+
807+
assert(product, "product not created");
808+
809+
const type = await supertest(ctMock.app)
810+
.post("/dummy/types")
811+
.send({
812+
key: "my-type",
813+
name: {
814+
en: "My Type",
815+
},
816+
description: {
817+
en: "My Type Description",
818+
},
819+
fieldDefinitions: [
820+
{
821+
name: "foo",
822+
label: {
823+
en: "foo",
824+
},
825+
required: false,
826+
type: {
827+
name: "String",
828+
},
829+
inputHint: "SingleLine",
830+
},
831+
],
832+
})
833+
.then((x) => x.body);
834+
835+
assert(type, "type not created");
836+
837+
const myCart = await supertest(ctMock.app)
838+
.post("/dummy/carts")
839+
.send({
840+
currency: "EUR",
841+
lineItems: [
842+
{
843+
sku: product.masterData.current.masterVariant.sku,
844+
quantity: 1,
845+
},
846+
],
847+
})
848+
.then((x) => x.body);
849+
850+
const lineItem = myCart.lineItems[0];
851+
assert(lineItem, "lineItem not created");
852+
853+
const response = await supertest(ctMock.app)
854+
.post(`/dummy/carts/${myCart.id}`)
855+
.send({
856+
version: myCart.version,
857+
actions: [
858+
{
859+
action: "setLineItemCustomType",
860+
lineItemId: lineItem.id,
861+
type: {
862+
typeId: "type",
863+
key: "my-type",
864+
},
865+
},
866+
],
867+
});
868+
869+
expect(response.status).toBe(200);
870+
expect(response.body.version).toBe(2);
871+
expect(response.body.lineItems).toMatchObject([
872+
{
873+
id: lineItem.id,
874+
custom: {
875+
type: {
876+
typeId: "type",
877+
id: type.id,
878+
},
879+
},
880+
},
881+
]);
882+
});
883+
714884
test("setCustomerEmail", async () => {
715885
assert(cart, "cart not created");
716886

0 commit comments

Comments
 (0)