order.e2e-spec.ts 70 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921
  1. /* tslint:disable:no-non-null-assertion */
  2. import { pick } from '@vendure/common/lib/pick';
  3. import { manualFulfillmentHandler } from '@vendure/core';
  4. import {
  5. createErrorResultGuard,
  6. createTestEnvironment,
  7. ErrorResultGuard,
  8. SimpleGraphQLClient,
  9. } from '@vendure/testing';
  10. import gql from 'graphql-tag';
  11. import path from 'path';
  12. import { initialData } from '../../../e2e-common/e2e-initial-data';
  13. import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
  14. import {
  15. failsToSettlePaymentMethod,
  16. onTransitionSpy,
  17. singleStageRefundablePaymentMethod,
  18. twoStagePaymentMethod,
  19. } from './fixtures/test-payment-methods';
  20. import { FULFILLMENT_FRAGMENT } from './graphql/fragments';
  21. import {
  22. AddNoteToOrder,
  23. CanceledOrderFragment,
  24. CancelOrder,
  25. CreateFulfillment,
  26. DeleteOrderNote,
  27. ErrorCode,
  28. FulfillmentFragment,
  29. GetCustomerList,
  30. GetOrder,
  31. GetOrderFulfillmentItems,
  32. GetOrderFulfillments,
  33. GetOrderHistory,
  34. GetOrderList,
  35. GetOrderListFulfillments,
  36. GetOrderListWithQty,
  37. GetOrderWithPayments,
  38. GetProductWithVariants,
  39. GetStockMovement,
  40. GlobalFlag,
  41. HistoryEntryType,
  42. OrderLineInput,
  43. PaymentFragment,
  44. RefundFragment,
  45. RefundOrder,
  46. SettlePayment,
  47. SettleRefund,
  48. SortOrder,
  49. StockMovementType,
  50. TransitFulfillment,
  51. UpdateOrderNote,
  52. UpdateProductVariants,
  53. } from './graphql/generated-e2e-admin-types';
  54. import {
  55. AddItemToOrder,
  56. ApplyCouponCode,
  57. DeletionResult,
  58. GetActiveOrder,
  59. GetOrderByCodeWithPayments,
  60. TestOrderFragmentFragment,
  61. UpdatedOrder,
  62. UpdatedOrderFragment,
  63. } from './graphql/generated-e2e-shop-types';
  64. import {
  65. CANCEL_ORDER,
  66. CREATE_FULFILLMENT,
  67. GET_CUSTOMER_LIST,
  68. GET_ORDER,
  69. GET_ORDERS_LIST,
  70. GET_ORDER_FULFILLMENTS,
  71. GET_ORDER_HISTORY,
  72. GET_PRODUCT_WITH_VARIANTS,
  73. GET_STOCK_MOVEMENT,
  74. SETTLE_PAYMENT,
  75. TRANSIT_FULFILLMENT,
  76. UPDATE_PRODUCT_VARIANTS,
  77. } from './graphql/shared-definitions';
  78. import {
  79. ADD_ITEM_TO_ORDER,
  80. APPLY_COUPON_CODE,
  81. GET_ACTIVE_ORDER,
  82. GET_ORDER_BY_CODE_WITH_PAYMENTS,
  83. } from './graphql/shop-definitions';
  84. import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
  85. import { addPaymentToOrder, proceedToArrangingPayment, sortById } from './utils/test-order-utils';
  86. describe('Orders resolver', () => {
  87. const { server, adminClient, shopClient } = createTestEnvironment({
  88. ...testConfig,
  89. paymentOptions: {
  90. paymentMethodHandlers: [
  91. twoStagePaymentMethod,
  92. failsToSettlePaymentMethod,
  93. singleStageRefundablePaymentMethod,
  94. ],
  95. },
  96. });
  97. let customers: GetCustomerList.Items[];
  98. const password = 'test';
  99. const orderGuard: ErrorResultGuard<
  100. TestOrderFragmentFragment | CanceledOrderFragment | UpdatedOrderFragment
  101. > = createErrorResultGuard(input => !!input.lines);
  102. const paymentGuard: ErrorResultGuard<PaymentFragment> = createErrorResultGuard(input => !!input.state);
  103. const fulfillmentGuard: ErrorResultGuard<FulfillmentFragment> = createErrorResultGuard(
  104. input => !!input.method,
  105. );
  106. const refundGuard: ErrorResultGuard<RefundFragment> = createErrorResultGuard(input => !!input.items);
  107. beforeAll(async () => {
  108. await server.init({
  109. initialData,
  110. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
  111. customerCount: 3,
  112. });
  113. await adminClient.asSuperAdmin();
  114. // Create a couple of orders to be queried
  115. const result = await adminClient.query<GetCustomerList.Query, GetCustomerList.Variables>(
  116. GET_CUSTOMER_LIST,
  117. {
  118. options: {
  119. take: 3,
  120. },
  121. },
  122. );
  123. customers = result.customers.items;
  124. await shopClient.asUserWithCredentials(customers[0].emailAddress, password);
  125. await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
  126. productVariantId: 'T_1',
  127. quantity: 1,
  128. });
  129. await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
  130. productVariantId: 'T_2',
  131. quantity: 1,
  132. });
  133. await shopClient.asUserWithCredentials(customers[1].emailAddress, password);
  134. await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
  135. productVariantId: 'T_2',
  136. quantity: 1,
  137. });
  138. await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(ADD_ITEM_TO_ORDER, {
  139. productVariantId: 'T_3',
  140. quantity: 3,
  141. });
  142. }, TEST_SETUP_TIMEOUT_MS);
  143. afterAll(async () => {
  144. await server.destroy();
  145. });
  146. it('order history initially contains Created -> AddingItems transition', async () => {
  147. const { order } = await adminClient.query<GetOrderHistory.Query, GetOrderHistory.Variables>(
  148. GET_ORDER_HISTORY,
  149. { id: 'T_1' },
  150. );
  151. expect(order!.history.totalItems).toBe(1);
  152. expect(order!.history.items.map(pick(['type', 'data']))).toEqual([
  153. {
  154. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  155. data: {
  156. from: 'Created',
  157. to: 'AddingItems',
  158. },
  159. },
  160. ]);
  161. });
  162. describe('querying', () => {
  163. it('orders', async () => {
  164. const result = await adminClient.query<GetOrderList.Query>(GET_ORDERS_LIST);
  165. expect(result.orders.items.map(o => o.id).sort()).toEqual(['T_1', 'T_2']);
  166. });
  167. it('order', async () => {
  168. const result = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  169. id: 'T_2',
  170. });
  171. expect(result.order!.id).toBe('T_2');
  172. });
  173. it('sort by total', async () => {
  174. const result = await adminClient.query<GetOrderList.Query, GetOrderList.Variables>(
  175. GET_ORDERS_LIST,
  176. {
  177. options: {
  178. sort: {
  179. total: SortOrder.DESC,
  180. },
  181. },
  182. },
  183. );
  184. expect(result.orders.items.map(o => pick(o, ['id', 'total']))).toEqual([
  185. { id: 'T_2', total: 799600 },
  186. { id: 'T_1', total: 269800 },
  187. ]);
  188. });
  189. it('filter by totalWithTax', async () => {
  190. const result = await adminClient.query<GetOrderList.Query, GetOrderList.Variables>(
  191. GET_ORDERS_LIST,
  192. {
  193. options: {
  194. filter: {
  195. totalWithTax: { gt: 323760 },
  196. },
  197. },
  198. },
  199. );
  200. expect(result.orders.items.map(o => pick(o, ['id', 'totalWithTax']))).toEqual([
  201. { id: 'T_2', totalWithTax: 959520 },
  202. ]);
  203. });
  204. it('sort by totalQuantity', async () => {
  205. const result = await adminClient.query<GetOrderList.Query, GetOrderList.Variables>(
  206. GET_ORDERS_LIST,
  207. {
  208. options: {
  209. sort: {
  210. totalQuantity: SortOrder.DESC,
  211. },
  212. },
  213. },
  214. );
  215. expect(result.orders.items.map(o => pick(o, ['id', 'totalQuantity']))).toEqual([
  216. { id: 'T_2', totalQuantity: 4 },
  217. { id: 'T_1', totalQuantity: 2 },
  218. ]);
  219. });
  220. it('filter by totalQuantity', async () => {
  221. const result = await adminClient.query<GetOrderList.Query, GetOrderList.Variables>(
  222. GET_ORDERS_LIST,
  223. {
  224. options: {
  225. filter: {
  226. totalQuantity: { eq: 4 },
  227. },
  228. },
  229. },
  230. );
  231. expect(result.orders.items.map(o => pick(o, ['id', 'totalQuantity']))).toEqual([
  232. { id: 'T_2', totalQuantity: 4 },
  233. ]);
  234. });
  235. });
  236. describe('payments', () => {
  237. let firstOrderCode: string;
  238. let firstOrderId: string;
  239. it('settlePayment fails', async () => {
  240. await shopClient.asUserWithCredentials(customers[0].emailAddress, password);
  241. await proceedToArrangingPayment(shopClient);
  242. const order = await addPaymentToOrder(shopClient, failsToSettlePaymentMethod);
  243. orderGuard.assertSuccess(order);
  244. expect(order.state).toBe('PaymentAuthorized');
  245. const payment = order.payments![0];
  246. const { settlePayment } = await adminClient.query<
  247. SettlePayment.Mutation,
  248. SettlePayment.Variables
  249. >(SETTLE_PAYMENT, {
  250. id: payment.id,
  251. });
  252. paymentGuard.assertErrorResult(settlePayment);
  253. expect(settlePayment.message).toBe('Settling the payment failed');
  254. expect(settlePayment.errorCode).toBe(ErrorCode.SETTLE_PAYMENT_ERROR);
  255. expect((settlePayment as any).paymentErrorMessage).toBe('Something went horribly wrong');
  256. const result = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  257. id: order.id,
  258. });
  259. expect(result.order!.state).toBe('PaymentAuthorized');
  260. firstOrderCode = order.code;
  261. firstOrderId = order.id;
  262. });
  263. it('public payment metadata available in Shop API', async () => {
  264. const { orderByCode } = await shopClient.query<
  265. GetOrderByCodeWithPayments.Query,
  266. GetOrderByCodeWithPayments.Variables
  267. >(GET_ORDER_BY_CODE_WITH_PAYMENTS, { code: firstOrderCode });
  268. expect(orderByCode?.payments?.[0].metadata).toEqual({
  269. public: {
  270. publicCreatePaymentData: 'public',
  271. publicSettlePaymentData: 'public',
  272. },
  273. });
  274. });
  275. it('public and private payment metadata available in Admin API', async () => {
  276. const { order } = await adminClient.query<
  277. GetOrderWithPayments.Query,
  278. GetOrderWithPayments.Variables
  279. >(GET_ORDER_WITH_PAYMENTS, { id: firstOrderId });
  280. expect(order?.payments?.[0].metadata).toEqual({
  281. privateCreatePaymentData: 'secret',
  282. privateSettlePaymentData: 'secret',
  283. public: {
  284. publicCreatePaymentData: 'public',
  285. publicSettlePaymentData: 'public',
  286. },
  287. });
  288. });
  289. it('settlePayment succeeds, onStateTransitionStart called', async () => {
  290. onTransitionSpy.mockClear();
  291. await shopClient.asUserWithCredentials(customers[1].emailAddress, password);
  292. await proceedToArrangingPayment(shopClient);
  293. const order = await addPaymentToOrder(shopClient, twoStagePaymentMethod);
  294. orderGuard.assertSuccess(order);
  295. expect(order.state).toBe('PaymentAuthorized');
  296. expect(onTransitionSpy).toHaveBeenCalledTimes(1);
  297. expect(onTransitionSpy.mock.calls[0][0]).toBe('Created');
  298. expect(onTransitionSpy.mock.calls[0][1]).toBe('Authorized');
  299. const payment = order.payments![0];
  300. const { settlePayment } = await adminClient.query<
  301. SettlePayment.Mutation,
  302. SettlePayment.Variables
  303. >(SETTLE_PAYMENT, {
  304. id: payment.id,
  305. });
  306. paymentGuard.assertSuccess(settlePayment);
  307. expect(settlePayment!.id).toBe(payment.id);
  308. expect(settlePayment!.state).toBe('Settled');
  309. // further metadata is combined into existing object
  310. expect(settlePayment!.metadata).toEqual({
  311. moreData: 42,
  312. public: {
  313. baz: 'quux',
  314. },
  315. });
  316. expect(onTransitionSpy).toHaveBeenCalledTimes(2);
  317. expect(onTransitionSpy.mock.calls[1][0]).toBe('Authorized');
  318. expect(onTransitionSpy.mock.calls[1][1]).toBe('Settled');
  319. const result = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  320. id: order.id,
  321. });
  322. expect(result.order!.state).toBe('PaymentSettled');
  323. expect(result.order!.payments![0].state).toBe('Settled');
  324. });
  325. it('order history contains expected entries', async () => {
  326. const { order } = await adminClient.query<GetOrderHistory.Query, GetOrderHistory.Variables>(
  327. GET_ORDER_HISTORY,
  328. { id: 'T_2', options: { sort: { id: SortOrder.ASC } } },
  329. );
  330. expect(order!.history.items.map(pick(['type', 'data']))).toEqual([
  331. {
  332. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  333. data: {
  334. from: 'Created',
  335. to: 'AddingItems',
  336. },
  337. },
  338. {
  339. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  340. data: {
  341. from: 'AddingItems',
  342. to: 'ArrangingPayment',
  343. },
  344. },
  345. {
  346. type: HistoryEntryType.ORDER_PAYMENT_TRANSITION,
  347. data: {
  348. paymentId: 'T_2',
  349. from: 'Created',
  350. to: 'Authorized',
  351. },
  352. },
  353. {
  354. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  355. data: {
  356. from: 'ArrangingPayment',
  357. to: 'PaymentAuthorized',
  358. },
  359. },
  360. {
  361. type: HistoryEntryType.ORDER_PAYMENT_TRANSITION,
  362. data: {
  363. paymentId: 'T_2',
  364. from: 'Authorized',
  365. to: 'Settled',
  366. },
  367. },
  368. {
  369. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  370. data: {
  371. from: 'PaymentAuthorized',
  372. to: 'PaymentSettled',
  373. },
  374. },
  375. ]);
  376. });
  377. });
  378. describe('fulfillment', () => {
  379. const orderId = 'T_2';
  380. let f1Id: string;
  381. let f2Id: string;
  382. let f3Id: string;
  383. it('return error result if lines is empty', async () => {
  384. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  385. id: orderId,
  386. });
  387. expect(order!.state).toBe('PaymentSettled');
  388. const { addFulfillmentToOrder } = await adminClient.query<
  389. CreateFulfillment.Mutation,
  390. CreateFulfillment.Variables
  391. >(CREATE_FULFILLMENT, {
  392. input: {
  393. lines: [],
  394. handler: {
  395. code: manualFulfillmentHandler.code,
  396. arguments: [{ name: 'method', value: 'Test' }],
  397. },
  398. },
  399. });
  400. fulfillmentGuard.assertErrorResult(addFulfillmentToOrder);
  401. expect(addFulfillmentToOrder.message).toBe('At least one OrderLine must be specified');
  402. expect(addFulfillmentToOrder.errorCode).toBe(ErrorCode.EMPTY_ORDER_LINE_SELECTION_ERROR);
  403. });
  404. it('returns error result if all quantities are zero', async () => {
  405. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  406. id: orderId,
  407. });
  408. expect(order!.state).toBe('PaymentSettled');
  409. const { addFulfillmentToOrder } = await adminClient.query<
  410. CreateFulfillment.Mutation,
  411. CreateFulfillment.Variables
  412. >(CREATE_FULFILLMENT, {
  413. input: {
  414. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 0 })),
  415. handler: {
  416. code: manualFulfillmentHandler.code,
  417. arguments: [{ name: 'method', value: 'Test' }],
  418. },
  419. },
  420. });
  421. fulfillmentGuard.assertErrorResult(addFulfillmentToOrder);
  422. expect(addFulfillmentToOrder.message).toBe('At least one OrderLine must be specified');
  423. expect(addFulfillmentToOrder.errorCode).toBe(ErrorCode.EMPTY_ORDER_LINE_SELECTION_ERROR);
  424. });
  425. it('creates the first fulfillment', async () => {
  426. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  427. id: orderId,
  428. });
  429. expect(order!.state).toBe('PaymentSettled');
  430. const lines = order!.lines;
  431. const { addFulfillmentToOrder } = await adminClient.query<
  432. CreateFulfillment.Mutation,
  433. CreateFulfillment.Variables
  434. >(CREATE_FULFILLMENT, {
  435. input: {
  436. lines: [{ orderLineId: lines[0].id, quantity: lines[0].quantity }],
  437. handler: {
  438. code: manualFulfillmentHandler.code,
  439. arguments: [
  440. { name: 'method', value: 'Test1' },
  441. { name: 'trackingCode', value: '111' },
  442. ],
  443. },
  444. },
  445. });
  446. fulfillmentGuard.assertSuccess(addFulfillmentToOrder);
  447. expect(addFulfillmentToOrder.id).toBe('T_1');
  448. expect(addFulfillmentToOrder.method).toBe('Test1');
  449. expect(addFulfillmentToOrder.trackingCode).toBe('111');
  450. expect(addFulfillmentToOrder.state).toBe('Pending');
  451. expect(addFulfillmentToOrder.orderItems).toEqual([{ id: lines[0].items[0].id }]);
  452. f1Id = addFulfillmentToOrder.id;
  453. const result = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  454. id: orderId,
  455. });
  456. expect(result.order!.lines[0].items[0].fulfillment!.id).toBe(addFulfillmentToOrder!.id);
  457. expect(
  458. result.order!.lines[1].items.filter(
  459. i => i.fulfillment && i.fulfillment.id === addFulfillmentToOrder.id,
  460. ).length,
  461. ).toBe(0);
  462. expect(result.order!.lines[1].items.filter(i => i.fulfillment == null).length).toBe(3);
  463. });
  464. it('creates the second fulfillment', async () => {
  465. const lines = await getUnfulfilledOrderLineInput(adminClient, orderId);
  466. const { addFulfillmentToOrder } = await adminClient.query<
  467. CreateFulfillment.Mutation,
  468. CreateFulfillment.Variables
  469. >(CREATE_FULFILLMENT, {
  470. input: {
  471. lines,
  472. handler: {
  473. code: manualFulfillmentHandler.code,
  474. arguments: [
  475. { name: 'method', value: 'Test2' },
  476. { name: 'trackingCode', value: '222' },
  477. ],
  478. },
  479. },
  480. });
  481. fulfillmentGuard.assertSuccess(addFulfillmentToOrder);
  482. expect(addFulfillmentToOrder.id).toBe('T_2');
  483. expect(addFulfillmentToOrder.method).toBe('Test2');
  484. expect(addFulfillmentToOrder.trackingCode).toBe('222');
  485. expect(addFulfillmentToOrder.state).toBe('Pending');
  486. f2Id = addFulfillmentToOrder.id;
  487. });
  488. it('cancels second fulfillment', async () => {
  489. const { transitionFulfillmentToState } = await adminClient.query<
  490. TransitFulfillment.Mutation,
  491. TransitFulfillment.Variables
  492. >(TRANSIT_FULFILLMENT, {
  493. id: f2Id,
  494. state: 'Cancelled',
  495. });
  496. fulfillmentGuard.assertSuccess(transitionFulfillmentToState);
  497. expect(transitionFulfillmentToState.id).toBe('T_2');
  498. expect(transitionFulfillmentToState.state).toBe('Cancelled');
  499. });
  500. it('order.fulfillments still lists second (cancelled) fulfillment', async () => {
  501. const { order } = await adminClient.query<
  502. GetOrderFulfillments.Query,
  503. GetOrderFulfillments.Variables
  504. >(GET_ORDER_FULFILLMENTS, {
  505. id: orderId,
  506. });
  507. expect(order?.fulfillments?.map(pick(['id', 'state']))).toEqual([
  508. { id: f1Id, state: 'Pending' },
  509. { id: f2Id, state: 'Cancelled' },
  510. ]);
  511. });
  512. it('creates third fulfillment with same items from second fulfillment', async () => {
  513. const lines = await getUnfulfilledOrderLineInput(adminClient, orderId);
  514. const { addFulfillmentToOrder } = await adminClient.query<
  515. CreateFulfillment.Mutation,
  516. CreateFulfillment.Variables
  517. >(CREATE_FULFILLMENT, {
  518. input: {
  519. lines,
  520. handler: {
  521. code: manualFulfillmentHandler.code,
  522. arguments: [
  523. { name: 'method', value: 'Test3' },
  524. { name: 'trackingCode', value: '333' },
  525. ],
  526. },
  527. },
  528. });
  529. fulfillmentGuard.assertSuccess(addFulfillmentToOrder);
  530. expect(addFulfillmentToOrder.id).toBe('T_3');
  531. expect(addFulfillmentToOrder.method).toBe('Test3');
  532. expect(addFulfillmentToOrder.trackingCode).toBe('333');
  533. expect(addFulfillmentToOrder.state).toBe('Pending');
  534. f3Id = addFulfillmentToOrder.id;
  535. });
  536. it('returns error result if an OrderItem already part of a Fulfillment', async () => {
  537. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  538. id: orderId,
  539. });
  540. const { addFulfillmentToOrder } = await adminClient.query<
  541. CreateFulfillment.Mutation,
  542. CreateFulfillment.Variables
  543. >(CREATE_FULFILLMENT, {
  544. input: {
  545. lines: [
  546. {
  547. orderLineId: order!.lines[0].id,
  548. quantity: 1,
  549. },
  550. ],
  551. handler: {
  552. code: manualFulfillmentHandler.code,
  553. arguments: [{ name: 'method', value: 'Test' }],
  554. },
  555. },
  556. });
  557. fulfillmentGuard.assertErrorResult(addFulfillmentToOrder);
  558. expect(addFulfillmentToOrder.message).toBe(
  559. 'One or more OrderItems are already part of a Fulfillment',
  560. );
  561. expect(addFulfillmentToOrder.errorCode).toBe(ErrorCode.ITEMS_ALREADY_FULFILLED_ERROR);
  562. });
  563. it('transitions the first fulfillment from created to Shipped and automatically change the order state to PartiallyShipped', async () => {
  564. const { transitionFulfillmentToState } = await adminClient.query<
  565. TransitFulfillment.Mutation,
  566. TransitFulfillment.Variables
  567. >(TRANSIT_FULFILLMENT, {
  568. id: f1Id,
  569. state: 'Shipped',
  570. });
  571. fulfillmentGuard.assertSuccess(transitionFulfillmentToState);
  572. expect(transitionFulfillmentToState.id).toBe(f1Id);
  573. expect(transitionFulfillmentToState.state).toBe('Shipped');
  574. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  575. id: orderId,
  576. });
  577. expect(order?.state).toBe('PartiallyShipped');
  578. });
  579. it('transitions the third fulfillment from created to Shipped and automatically change the order state to Shipped', async () => {
  580. const { transitionFulfillmentToState } = await adminClient.query<
  581. TransitFulfillment.Mutation,
  582. TransitFulfillment.Variables
  583. >(TRANSIT_FULFILLMENT, {
  584. id: f3Id,
  585. state: 'Shipped',
  586. });
  587. fulfillmentGuard.assertSuccess(transitionFulfillmentToState);
  588. expect(transitionFulfillmentToState.id).toBe(f3Id);
  589. expect(transitionFulfillmentToState.state).toBe('Shipped');
  590. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  591. id: orderId,
  592. });
  593. expect(order?.state).toBe('Shipped');
  594. });
  595. it('transitions the first fulfillment from Shipped to Delivered and change the order state to PartiallyDelivered', async () => {
  596. const { transitionFulfillmentToState } = await adminClient.query<
  597. TransitFulfillment.Mutation,
  598. TransitFulfillment.Variables
  599. >(TRANSIT_FULFILLMENT, {
  600. id: f1Id,
  601. state: 'Delivered',
  602. });
  603. fulfillmentGuard.assertSuccess(transitionFulfillmentToState);
  604. expect(transitionFulfillmentToState.id).toBe(f1Id);
  605. expect(transitionFulfillmentToState.state).toBe('Delivered');
  606. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  607. id: orderId,
  608. });
  609. expect(order?.state).toBe('PartiallyDelivered');
  610. });
  611. it('transitions the third fulfillment from Shipped to Delivered and change the order state to Delivered', async () => {
  612. const { transitionFulfillmentToState } = await adminClient.query<
  613. TransitFulfillment.Mutation,
  614. TransitFulfillment.Variables
  615. >(TRANSIT_FULFILLMENT, {
  616. id: f3Id,
  617. state: 'Delivered',
  618. });
  619. fulfillmentGuard.assertSuccess(transitionFulfillmentToState);
  620. expect(transitionFulfillmentToState.id).toBe(f3Id);
  621. expect(transitionFulfillmentToState.state).toBe('Delivered');
  622. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  623. id: orderId,
  624. });
  625. expect(order?.state).toBe('Delivered');
  626. });
  627. it('order history contains expected entries', async () => {
  628. const { order } = await adminClient.query<GetOrderHistory.Query, GetOrderHistory.Variables>(
  629. GET_ORDER_HISTORY,
  630. {
  631. id: orderId,
  632. options: {
  633. skip: 6,
  634. },
  635. },
  636. );
  637. expect(order!.history.items.map(pick(['type', 'data']))).toEqual([
  638. {
  639. data: {
  640. fulfillmentId: f1Id,
  641. },
  642. type: HistoryEntryType.ORDER_FULFILLMENT,
  643. },
  644. {
  645. data: {
  646. from: 'Created',
  647. fulfillmentId: f1Id,
  648. to: 'Pending',
  649. },
  650. type: HistoryEntryType.ORDER_FULFILLMENT_TRANSITION,
  651. },
  652. {
  653. data: {
  654. fulfillmentId: f2Id,
  655. },
  656. type: HistoryEntryType.ORDER_FULFILLMENT,
  657. },
  658. {
  659. data: {
  660. from: 'Created',
  661. fulfillmentId: f2Id,
  662. to: 'Pending',
  663. },
  664. type: HistoryEntryType.ORDER_FULFILLMENT_TRANSITION,
  665. },
  666. {
  667. data: {
  668. from: 'Pending',
  669. fulfillmentId: f2Id,
  670. to: 'Cancelled',
  671. },
  672. type: HistoryEntryType.ORDER_FULFILLMENT_TRANSITION,
  673. },
  674. {
  675. data: {
  676. fulfillmentId: f3Id,
  677. },
  678. type: HistoryEntryType.ORDER_FULFILLMENT,
  679. },
  680. {
  681. data: {
  682. from: 'Created',
  683. fulfillmentId: f3Id,
  684. to: 'Pending',
  685. },
  686. type: HistoryEntryType.ORDER_FULFILLMENT_TRANSITION,
  687. },
  688. {
  689. data: {
  690. from: 'Pending',
  691. fulfillmentId: f1Id,
  692. to: 'Shipped',
  693. },
  694. type: HistoryEntryType.ORDER_FULFILLMENT_TRANSITION,
  695. },
  696. {
  697. data: {
  698. from: 'PaymentSettled',
  699. to: 'PartiallyShipped',
  700. },
  701. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  702. },
  703. {
  704. data: {
  705. from: 'Pending',
  706. fulfillmentId: f3Id,
  707. to: 'Shipped',
  708. },
  709. type: HistoryEntryType.ORDER_FULFILLMENT_TRANSITION,
  710. },
  711. {
  712. data: {
  713. from: 'PartiallyShipped',
  714. to: 'Shipped',
  715. },
  716. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  717. },
  718. {
  719. data: {
  720. from: 'Shipped',
  721. fulfillmentId: f1Id,
  722. to: 'Delivered',
  723. },
  724. type: HistoryEntryType.ORDER_FULFILLMENT_TRANSITION,
  725. },
  726. {
  727. data: {
  728. from: 'Shipped',
  729. to: 'PartiallyDelivered',
  730. },
  731. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  732. },
  733. {
  734. data: {
  735. from: 'Shipped',
  736. fulfillmentId: f3Id,
  737. to: 'Delivered',
  738. },
  739. type: HistoryEntryType.ORDER_FULFILLMENT_TRANSITION,
  740. },
  741. {
  742. data: {
  743. from: 'PartiallyDelivered',
  744. to: 'Delivered',
  745. },
  746. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  747. },
  748. ]);
  749. });
  750. it('order.fulfillments resolver for single order', async () => {
  751. const { order } = await adminClient.query<
  752. GetOrderFulfillments.Query,
  753. GetOrderFulfillments.Variables
  754. >(GET_ORDER_FULFILLMENTS, {
  755. id: orderId,
  756. });
  757. expect(order!.fulfillments?.sort(sortById)).toEqual([
  758. { id: f1Id, method: 'Test1', state: 'Delivered', nextStates: ['Cancelled'] },
  759. { id: f2Id, method: 'Test2', state: 'Cancelled', nextStates: [] },
  760. { id: f3Id, method: 'Test3', state: 'Delivered', nextStates: ['Cancelled'] },
  761. ]);
  762. });
  763. it('order.fulfillments resolver for order list', async () => {
  764. const { orders } = await adminClient.query<GetOrderListFulfillments.Query>(
  765. GET_ORDER_LIST_FULFILLMENTS,
  766. );
  767. expect(orders.items[0].fulfillments).toEqual([]);
  768. expect(orders.items[1].fulfillments).toEqual([
  769. { id: f1Id, method: 'Test1', state: 'Delivered', nextStates: ['Cancelled'] },
  770. { id: f2Id, method: 'Test2', state: 'Cancelled', nextStates: [] },
  771. { id: f3Id, method: 'Test3', state: 'Delivered', nextStates: ['Cancelled'] },
  772. ]);
  773. });
  774. it('order.fulfillments.orderItems resolver', async () => {
  775. const { order } = await adminClient.query<
  776. GetOrderFulfillmentItems.Query,
  777. GetOrderFulfillmentItems.Variables
  778. >(GET_ORDER_FULFILLMENT_ITEMS, {
  779. id: orderId,
  780. });
  781. expect(order!.fulfillments![0].orderItems).toEqual([{ id: 'T_3' }]);
  782. expect(order!.fulfillments![1].orderItems).toEqual([{ id: 'T_4' }, { id: 'T_5' }, { id: 'T_6' }]);
  783. expect(order!.fulfillments![2].orderItems).toEqual([{ id: 'T_4' }, { id: 'T_5' }, { id: 'T_6' }]);
  784. });
  785. });
  786. describe('cancellation by orderId', () => {
  787. it('cancel from AddingItems state', async () => {
  788. const testOrder = await createTestOrder(
  789. adminClient,
  790. shopClient,
  791. customers[0].emailAddress,
  792. password,
  793. );
  794. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  795. id: testOrder.orderId,
  796. });
  797. expect(order!.state).toBe('AddingItems');
  798. const { cancelOrder } = await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(
  799. CANCEL_ORDER,
  800. {
  801. input: {
  802. orderId: testOrder.orderId,
  803. },
  804. },
  805. );
  806. const { order: order2 } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  807. id: testOrder.orderId,
  808. });
  809. expect(order2!.state).toBe('Cancelled');
  810. expect(order2!.active).toBe(false);
  811. await assertNoStockMovementsCreated(testOrder.product.id);
  812. });
  813. it('cancel from ArrangingPayment state', async () => {
  814. const testOrder = await createTestOrder(
  815. adminClient,
  816. shopClient,
  817. customers[0].emailAddress,
  818. password,
  819. );
  820. await proceedToArrangingPayment(shopClient);
  821. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  822. id: testOrder.orderId,
  823. });
  824. expect(order!.state).toBe('ArrangingPayment');
  825. await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(CANCEL_ORDER, {
  826. input: {
  827. orderId: testOrder.orderId,
  828. },
  829. });
  830. const { order: order2 } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  831. id: testOrder.orderId,
  832. });
  833. expect(order2!.state).toBe('Cancelled');
  834. expect(order2!.active).toBe(false);
  835. await assertNoStockMovementsCreated(testOrder.product.id);
  836. });
  837. it('cancel from PaymentAuthorized state', async () => {
  838. const testOrder = await createTestOrder(
  839. adminClient,
  840. shopClient,
  841. customers[0].emailAddress,
  842. password,
  843. );
  844. await proceedToArrangingPayment(shopClient);
  845. const order = await addPaymentToOrder(shopClient, failsToSettlePaymentMethod);
  846. orderGuard.assertSuccess(order);
  847. expect(order.state).toBe('PaymentAuthorized');
  848. const result1 = await adminClient.query<GetStockMovement.Query, GetStockMovement.Variables>(
  849. GET_STOCK_MOVEMENT,
  850. {
  851. id: 'T_3',
  852. },
  853. );
  854. let variant1 = result1.product!.variants[0];
  855. expect(variant1.stockOnHand).toBe(100);
  856. expect(variant1.stockAllocated).toBe(2);
  857. expect(variant1.stockMovements.items.map(pick(['type', 'quantity']))).toEqual([
  858. { type: StockMovementType.ADJUSTMENT, quantity: 100 },
  859. { type: StockMovementType.ALLOCATION, quantity: 2 },
  860. ]);
  861. const { cancelOrder } = await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(
  862. CANCEL_ORDER,
  863. {
  864. input: {
  865. orderId: testOrder.orderId,
  866. },
  867. },
  868. );
  869. orderGuard.assertSuccess(cancelOrder);
  870. expect(
  871. cancelOrder.lines.map(l =>
  872. l.items.map(pick(['id', 'cancelled'])).sort((a, b) => (a.id > b.id ? 1 : -1)),
  873. ),
  874. ).toEqual([
  875. [
  876. { id: 'T_11', cancelled: true },
  877. { id: 'T_12', cancelled: true },
  878. ],
  879. ]);
  880. const { order: order2 } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  881. id: testOrder.orderId,
  882. });
  883. expect(order2!.active).toBe(false);
  884. expect(order2!.state).toBe('Cancelled');
  885. const result2 = await adminClient.query<GetStockMovement.Query, GetStockMovement.Variables>(
  886. GET_STOCK_MOVEMENT,
  887. {
  888. id: 'T_3',
  889. },
  890. );
  891. variant1 = result2.product!.variants[0];
  892. expect(variant1.stockOnHand).toBe(100);
  893. expect(variant1.stockAllocated).toBe(0);
  894. expect(variant1.stockMovements.items.map(pick(['type', 'quantity']))).toEqual([
  895. { type: StockMovementType.ADJUSTMENT, quantity: 100 },
  896. { type: StockMovementType.ALLOCATION, quantity: 2 },
  897. { type: StockMovementType.RELEASE, quantity: 1 },
  898. { type: StockMovementType.RELEASE, quantity: 1 },
  899. ]);
  900. });
  901. async function assertNoStockMovementsCreated(productId: string) {
  902. const result = await adminClient.query<GetStockMovement.Query, GetStockMovement.Variables>(
  903. GET_STOCK_MOVEMENT,
  904. {
  905. id: productId,
  906. },
  907. );
  908. const variant2 = result.product!.variants[0];
  909. expect(variant2.stockOnHand).toBe(100);
  910. expect(variant2.stockMovements.items.map(pick(['type', 'quantity']))).toEqual([
  911. { type: StockMovementType.ADJUSTMENT, quantity: 100 },
  912. ]);
  913. }
  914. });
  915. describe('cancellation by OrderLine', () => {
  916. let orderId: string;
  917. let product: GetProductWithVariants.Product;
  918. let productVariantId: string;
  919. beforeAll(async () => {
  920. const result = await createTestOrder(
  921. adminClient,
  922. shopClient,
  923. customers[0].emailAddress,
  924. password,
  925. );
  926. orderId = result.orderId;
  927. product = result.product;
  928. productVariantId = result.productVariantId;
  929. });
  930. it('cannot cancel from AddingItems state', async () => {
  931. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  932. id: orderId,
  933. });
  934. expect(order!.state).toBe('AddingItems');
  935. const { cancelOrder } = await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(
  936. CANCEL_ORDER,
  937. {
  938. input: {
  939. orderId,
  940. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
  941. },
  942. },
  943. );
  944. orderGuard.assertErrorResult(cancelOrder);
  945. expect(cancelOrder.message).toBe(
  946. 'Cannot cancel OrderLines from an Order in the "AddingItems" state',
  947. );
  948. expect(cancelOrder.errorCode).toBe(ErrorCode.CANCEL_ACTIVE_ORDER_ERROR);
  949. });
  950. it('cannot cancel from ArrangingPayment state', async () => {
  951. await proceedToArrangingPayment(shopClient);
  952. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  953. id: orderId,
  954. });
  955. expect(order!.state).toBe('ArrangingPayment');
  956. const { cancelOrder } = await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(
  957. CANCEL_ORDER,
  958. {
  959. input: {
  960. orderId,
  961. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
  962. },
  963. },
  964. );
  965. orderGuard.assertErrorResult(cancelOrder);
  966. expect(cancelOrder.message).toBe(
  967. 'Cannot cancel OrderLines from an Order in the "ArrangingPayment" state',
  968. );
  969. expect(cancelOrder.errorCode).toBe(ErrorCode.CANCEL_ACTIVE_ORDER_ERROR);
  970. });
  971. it('returns error result if lines are empty', async () => {
  972. const order = await addPaymentToOrder(shopClient, twoStagePaymentMethod);
  973. orderGuard.assertSuccess(order);
  974. expect(order.state).toBe('PaymentAuthorized');
  975. const { cancelOrder } = await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(
  976. CANCEL_ORDER,
  977. {
  978. input: {
  979. orderId,
  980. lines: [],
  981. },
  982. },
  983. );
  984. orderGuard.assertErrorResult(cancelOrder);
  985. expect(cancelOrder.message).toBe('At least one OrderLine must be specified');
  986. expect(cancelOrder.errorCode).toBe(ErrorCode.EMPTY_ORDER_LINE_SELECTION_ERROR);
  987. });
  988. it('returns error result if all quantities zero', async () => {
  989. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  990. id: orderId,
  991. });
  992. const { cancelOrder } = await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(
  993. CANCEL_ORDER,
  994. {
  995. input: {
  996. orderId,
  997. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 0 })),
  998. },
  999. },
  1000. );
  1001. orderGuard.assertErrorResult(cancelOrder);
  1002. expect(cancelOrder.message).toBe('At least one OrderLine must be specified');
  1003. expect(cancelOrder.errorCode).toBe(ErrorCode.EMPTY_ORDER_LINE_SELECTION_ERROR);
  1004. });
  1005. it('partial cancellation', async () => {
  1006. const result1 = await adminClient.query<GetStockMovement.Query, GetStockMovement.Variables>(
  1007. GET_STOCK_MOVEMENT,
  1008. {
  1009. id: product.id,
  1010. },
  1011. );
  1012. const variant1 = result1.product!.variants[0];
  1013. expect(variant1.stockOnHand).toBe(100);
  1014. expect(variant1.stockAllocated).toBe(2);
  1015. expect(variant1.stockMovements.items.map(pick(['type', 'quantity']))).toEqual([
  1016. { type: StockMovementType.ADJUSTMENT, quantity: 100 },
  1017. { type: StockMovementType.ALLOCATION, quantity: 2 },
  1018. { type: StockMovementType.RELEASE, quantity: 1 },
  1019. { type: StockMovementType.RELEASE, quantity: 1 },
  1020. { type: StockMovementType.ALLOCATION, quantity: 2 },
  1021. ]);
  1022. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1023. id: orderId,
  1024. });
  1025. const { cancelOrder } = await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(
  1026. CANCEL_ORDER,
  1027. {
  1028. input: {
  1029. orderId,
  1030. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
  1031. reason: 'cancel reason 1',
  1032. },
  1033. },
  1034. );
  1035. orderGuard.assertSuccess(cancelOrder);
  1036. expect(cancelOrder.lines[0].quantity).toBe(1);
  1037. expect(cancelOrder.lines[0].items.sort((a, b) => (a.id < b.id ? -1 : 1))).toEqual([
  1038. { id: 'T_13', cancelled: true },
  1039. { id: 'T_14', cancelled: false },
  1040. ]);
  1041. const { order: order2 } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1042. id: orderId,
  1043. });
  1044. expect(order2!.state).toBe('PaymentAuthorized');
  1045. expect(order2!.lines[0].quantity).toBe(1);
  1046. const result2 = await adminClient.query<GetStockMovement.Query, GetStockMovement.Variables>(
  1047. GET_STOCK_MOVEMENT,
  1048. {
  1049. id: product.id,
  1050. },
  1051. );
  1052. const variant2 = result2.product!.variants[0];
  1053. expect(variant2.stockOnHand).toBe(100);
  1054. expect(variant2.stockAllocated).toBe(1);
  1055. expect(variant2.stockMovements.items.map(pick(['type', 'quantity']))).toEqual([
  1056. { type: StockMovementType.ADJUSTMENT, quantity: 100 },
  1057. { type: StockMovementType.ALLOCATION, quantity: 2 },
  1058. { type: StockMovementType.RELEASE, quantity: 1 },
  1059. { type: StockMovementType.RELEASE, quantity: 1 },
  1060. { type: StockMovementType.ALLOCATION, quantity: 2 },
  1061. { type: StockMovementType.RELEASE, quantity: 1 },
  1062. ]);
  1063. });
  1064. it('returns error result if attempting to cancel already cancelled item', async () => {
  1065. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1066. id: orderId,
  1067. });
  1068. const { cancelOrder } = await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(
  1069. CANCEL_ORDER,
  1070. {
  1071. input: {
  1072. orderId,
  1073. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 2 })),
  1074. },
  1075. },
  1076. );
  1077. orderGuard.assertErrorResult(cancelOrder);
  1078. expect(cancelOrder.message).toBe(
  1079. 'The specified quantity is greater than the available OrderItems',
  1080. );
  1081. expect(cancelOrder.errorCode).toBe(ErrorCode.QUANTITY_TOO_GREAT_ERROR);
  1082. });
  1083. it('complete cancellation', async () => {
  1084. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1085. id: orderId,
  1086. });
  1087. await adminClient.query<CancelOrder.Mutation, CancelOrder.Variables>(CANCEL_ORDER, {
  1088. input: {
  1089. orderId,
  1090. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
  1091. reason: 'cancel reason 2',
  1092. },
  1093. });
  1094. const { order: order2 } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1095. id: orderId,
  1096. });
  1097. expect(order2!.state).toBe('Cancelled');
  1098. const result = await adminClient.query<GetStockMovement.Query, GetStockMovement.Variables>(
  1099. GET_STOCK_MOVEMENT,
  1100. {
  1101. id: product.id,
  1102. },
  1103. );
  1104. const variant2 = result.product!.variants[0];
  1105. expect(variant2.stockOnHand).toBe(100);
  1106. expect(variant2.stockAllocated).toBe(0);
  1107. expect(variant2.stockMovements.items.map(pick(['type', 'quantity']))).toEqual([
  1108. { type: StockMovementType.ADJUSTMENT, quantity: 100 },
  1109. { type: StockMovementType.ALLOCATION, quantity: 2 },
  1110. { type: StockMovementType.RELEASE, quantity: 1 },
  1111. { type: StockMovementType.RELEASE, quantity: 1 },
  1112. { type: StockMovementType.ALLOCATION, quantity: 2 },
  1113. { type: StockMovementType.RELEASE, quantity: 1 },
  1114. { type: StockMovementType.RELEASE, quantity: 1 },
  1115. ]);
  1116. });
  1117. it('order history contains expected entries', async () => {
  1118. const { order } = await adminClient.query<GetOrderHistory.Query, GetOrderHistory.Variables>(
  1119. GET_ORDER_HISTORY,
  1120. {
  1121. id: orderId,
  1122. options: {
  1123. skip: 0,
  1124. },
  1125. },
  1126. );
  1127. expect(order!.history.items.map(pick(['type', 'data']))).toEqual([
  1128. {
  1129. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  1130. data: {
  1131. from: 'Created',
  1132. to: 'AddingItems',
  1133. },
  1134. },
  1135. {
  1136. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  1137. data: {
  1138. from: 'AddingItems',
  1139. to: 'ArrangingPayment',
  1140. },
  1141. },
  1142. {
  1143. type: HistoryEntryType.ORDER_PAYMENT_TRANSITION,
  1144. data: {
  1145. paymentId: 'T_4',
  1146. from: 'Created',
  1147. to: 'Authorized',
  1148. },
  1149. },
  1150. {
  1151. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  1152. data: {
  1153. from: 'ArrangingPayment',
  1154. to: 'PaymentAuthorized',
  1155. },
  1156. },
  1157. {
  1158. type: HistoryEntryType.ORDER_CANCELLATION,
  1159. data: {
  1160. orderItemIds: ['T_13'],
  1161. reason: 'cancel reason 1',
  1162. },
  1163. },
  1164. {
  1165. type: HistoryEntryType.ORDER_CANCELLATION,
  1166. data: {
  1167. orderItemIds: ['T_14'],
  1168. reason: 'cancel reason 2',
  1169. },
  1170. },
  1171. {
  1172. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  1173. data: {
  1174. from: 'PaymentAuthorized',
  1175. to: 'Cancelled',
  1176. },
  1177. },
  1178. ]);
  1179. });
  1180. });
  1181. describe('refunds', () => {
  1182. let orderId: string;
  1183. let product: GetProductWithVariants.Product;
  1184. let productVariantId: string;
  1185. let paymentId: string;
  1186. let refundId: string;
  1187. beforeAll(async () => {
  1188. const result = await createTestOrder(
  1189. adminClient,
  1190. shopClient,
  1191. customers[0].emailAddress,
  1192. password,
  1193. );
  1194. orderId = result.orderId;
  1195. product = result.product;
  1196. productVariantId = result.productVariantId;
  1197. });
  1198. it('cannot refund from PaymentAuthorized state', async () => {
  1199. await proceedToArrangingPayment(shopClient);
  1200. const order = await addPaymentToOrder(shopClient, twoStagePaymentMethod);
  1201. orderGuard.assertSuccess(order);
  1202. expect(order.state).toBe('PaymentAuthorized');
  1203. paymentId = order.payments![0].id;
  1204. const { refundOrder } = await adminClient.query<RefundOrder.Mutation, RefundOrder.Variables>(
  1205. REFUND_ORDER,
  1206. {
  1207. input: {
  1208. lines: order.lines.map(l => ({ orderLineId: l.id, quantity: 1 })),
  1209. shipping: 0,
  1210. adjustment: 0,
  1211. paymentId,
  1212. },
  1213. },
  1214. );
  1215. refundGuard.assertErrorResult(refundOrder);
  1216. expect(refundOrder.message).toBe('Cannot refund an Order in the "PaymentAuthorized" state');
  1217. expect(refundOrder.errorCode).toBe(ErrorCode.REFUND_ORDER_STATE_ERROR);
  1218. });
  1219. it('returns error result if no lines and no shipping', async () => {
  1220. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1221. id: orderId,
  1222. });
  1223. const { settlePayment } = await adminClient.query<
  1224. SettlePayment.Mutation,
  1225. SettlePayment.Variables
  1226. >(SETTLE_PAYMENT, {
  1227. id: order!.payments![0].id,
  1228. });
  1229. paymentGuard.assertSuccess(settlePayment);
  1230. expect(settlePayment!.state).toBe('Settled');
  1231. const { refundOrder } = await adminClient.query<RefundOrder.Mutation, RefundOrder.Variables>(
  1232. REFUND_ORDER,
  1233. {
  1234. input: {
  1235. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: 0 })),
  1236. shipping: 0,
  1237. adjustment: 0,
  1238. paymentId,
  1239. },
  1240. },
  1241. );
  1242. refundGuard.assertErrorResult(refundOrder);
  1243. expect(refundOrder.message).toBe('Nothing to refund');
  1244. expect(refundOrder.errorCode).toBe(ErrorCode.NOTHING_TO_REFUND_ERROR);
  1245. });
  1246. it(
  1247. 'throws if paymentId not valid',
  1248. assertThrowsWithMessage(async () => {
  1249. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1250. id: orderId,
  1251. });
  1252. const { refundOrder } = await adminClient.query<RefundOrder.Mutation, RefundOrder.Variables>(
  1253. REFUND_ORDER,
  1254. {
  1255. input: {
  1256. lines: [],
  1257. shipping: 100,
  1258. adjustment: 0,
  1259. paymentId: 'T_999',
  1260. },
  1261. },
  1262. );
  1263. }, `No Payment with the id '999' could be found`),
  1264. );
  1265. it('returns error result if payment and order lines do not belong to the same Order', async () => {
  1266. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1267. id: orderId,
  1268. });
  1269. const { refundOrder } = await adminClient.query<RefundOrder.Mutation, RefundOrder.Variables>(
  1270. REFUND_ORDER,
  1271. {
  1272. input: {
  1273. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: l.quantity })),
  1274. shipping: 100,
  1275. adjustment: 0,
  1276. paymentId: 'T_1',
  1277. },
  1278. },
  1279. );
  1280. refundGuard.assertErrorResult(refundOrder);
  1281. expect(refundOrder.message).toBe('The Payment and OrderLines do not belong to the same Order');
  1282. expect(refundOrder.errorCode).toBe(ErrorCode.PAYMENT_ORDER_MISMATCH_ERROR);
  1283. });
  1284. it('creates a Refund to be manually settled', async () => {
  1285. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1286. id: orderId,
  1287. });
  1288. const { refundOrder } = await adminClient.query<RefundOrder.Mutation, RefundOrder.Variables>(
  1289. REFUND_ORDER,
  1290. {
  1291. input: {
  1292. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: l.quantity })),
  1293. shipping: order!.shipping,
  1294. adjustment: 0,
  1295. reason: 'foo',
  1296. paymentId,
  1297. },
  1298. },
  1299. );
  1300. refundGuard.assertSuccess(refundOrder);
  1301. expect(refundOrder.shipping).toBe(order!.shipping);
  1302. expect(refundOrder.items).toBe(order!.subTotalWithTax);
  1303. expect(refundOrder.total).toBe(order!.totalWithTax);
  1304. expect(refundOrder.transactionId).toBe(null);
  1305. expect(refundOrder.state).toBe('Pending');
  1306. refundId = refundOrder.id;
  1307. });
  1308. it('returns error result if attempting to refund the same item more than once', async () => {
  1309. const { order } = await adminClient.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1310. id: orderId,
  1311. });
  1312. const { refundOrder } = await adminClient.query<RefundOrder.Mutation, RefundOrder.Variables>(
  1313. REFUND_ORDER,
  1314. {
  1315. input: {
  1316. lines: order!.lines.map(l => ({ orderLineId: l.id, quantity: l.quantity })),
  1317. shipping: order!.shipping,
  1318. adjustment: 0,
  1319. paymentId,
  1320. },
  1321. },
  1322. );
  1323. refundGuard.assertErrorResult(refundOrder);
  1324. expect(refundOrder.message).toBe('Cannot refund an OrderItem which has already been refunded');
  1325. expect(refundOrder.errorCode).toBe(ErrorCode.ALREADY_REFUNDED_ERROR);
  1326. });
  1327. it('manually settle a Refund', async () => {
  1328. const { settleRefund } = await adminClient.query<SettleRefund.Mutation, SettleRefund.Variables>(
  1329. SETTLE_REFUND,
  1330. {
  1331. input: {
  1332. id: refundId,
  1333. transactionId: 'aaabbb',
  1334. },
  1335. },
  1336. );
  1337. refundGuard.assertSuccess(settleRefund);
  1338. expect(settleRefund.state).toBe('Settled');
  1339. expect(settleRefund.transactionId).toBe('aaabbb');
  1340. });
  1341. it('order history contains expected entries', async () => {
  1342. const { order } = await adminClient.query<GetOrderHistory.Query, GetOrderHistory.Variables>(
  1343. GET_ORDER_HISTORY,
  1344. {
  1345. id: orderId,
  1346. options: {
  1347. skip: 0,
  1348. },
  1349. },
  1350. );
  1351. expect(order!.history.items.sort(sortById).map(pick(['type', 'data']))).toEqual([
  1352. {
  1353. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  1354. data: {
  1355. from: 'Created',
  1356. to: 'AddingItems',
  1357. },
  1358. },
  1359. {
  1360. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  1361. data: {
  1362. from: 'AddingItems',
  1363. to: 'ArrangingPayment',
  1364. },
  1365. },
  1366. {
  1367. type: HistoryEntryType.ORDER_PAYMENT_TRANSITION,
  1368. data: {
  1369. paymentId: 'T_5',
  1370. from: 'Created',
  1371. to: 'Authorized',
  1372. },
  1373. },
  1374. {
  1375. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  1376. data: {
  1377. from: 'ArrangingPayment',
  1378. to: 'PaymentAuthorized',
  1379. },
  1380. },
  1381. {
  1382. type: HistoryEntryType.ORDER_PAYMENT_TRANSITION,
  1383. data: {
  1384. paymentId: 'T_5',
  1385. from: 'Authorized',
  1386. to: 'Settled',
  1387. },
  1388. },
  1389. {
  1390. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  1391. data: {
  1392. from: 'PaymentAuthorized',
  1393. to: 'PaymentSettled',
  1394. },
  1395. },
  1396. {
  1397. type: HistoryEntryType.ORDER_REFUND_TRANSITION,
  1398. data: {
  1399. refundId: 'T_1',
  1400. reason: 'foo',
  1401. from: 'Pending',
  1402. to: 'Settled',
  1403. },
  1404. },
  1405. ]);
  1406. });
  1407. });
  1408. describe('order notes', () => {
  1409. let orderId: string;
  1410. let firstNoteId: string;
  1411. beforeAll(async () => {
  1412. const result = await createTestOrder(
  1413. adminClient,
  1414. shopClient,
  1415. customers[2].emailAddress,
  1416. password,
  1417. );
  1418. orderId = result.orderId;
  1419. });
  1420. it('private note', async () => {
  1421. const { addNoteToOrder } = await adminClient.query<
  1422. AddNoteToOrder.Mutation,
  1423. AddNoteToOrder.Variables
  1424. >(ADD_NOTE_TO_ORDER, {
  1425. input: {
  1426. id: orderId,
  1427. note: 'A private note',
  1428. isPublic: false,
  1429. },
  1430. });
  1431. expect(addNoteToOrder.id).toBe(orderId);
  1432. const { order } = await adminClient.query<GetOrderHistory.Query, GetOrderHistory.Variables>(
  1433. GET_ORDER_HISTORY,
  1434. {
  1435. id: orderId,
  1436. options: {
  1437. skip: 1,
  1438. },
  1439. },
  1440. );
  1441. expect(order!.history.items.map(pick(['type', 'data']))).toEqual([
  1442. {
  1443. type: HistoryEntryType.ORDER_NOTE,
  1444. data: {
  1445. note: 'A private note',
  1446. },
  1447. },
  1448. ]);
  1449. firstNoteId = order!.history.items[0].id;
  1450. const { activeOrder } = await shopClient.query<GetActiveOrder.Query>(GET_ACTIVE_ORDER);
  1451. expect(activeOrder!.history.items.map(pick(['type']))).toEqual([
  1452. { type: HistoryEntryType.ORDER_STATE_TRANSITION },
  1453. ]);
  1454. });
  1455. it('public note', async () => {
  1456. const { addNoteToOrder } = await adminClient.query<
  1457. AddNoteToOrder.Mutation,
  1458. AddNoteToOrder.Variables
  1459. >(ADD_NOTE_TO_ORDER, {
  1460. input: {
  1461. id: orderId,
  1462. note: 'A public note',
  1463. isPublic: true,
  1464. },
  1465. });
  1466. expect(addNoteToOrder.id).toBe(orderId);
  1467. const { order } = await adminClient.query<GetOrderHistory.Query, GetOrderHistory.Variables>(
  1468. GET_ORDER_HISTORY,
  1469. {
  1470. id: orderId,
  1471. options: {
  1472. skip: 2,
  1473. },
  1474. },
  1475. );
  1476. expect(order!.history.items.map(pick(['type', 'data']))).toEqual([
  1477. {
  1478. type: HistoryEntryType.ORDER_NOTE,
  1479. data: {
  1480. note: 'A public note',
  1481. },
  1482. },
  1483. ]);
  1484. const { activeOrder } = await shopClient.query<GetActiveOrder.Query>(GET_ACTIVE_ORDER);
  1485. expect(activeOrder!.history.items.map(pick(['type', 'data']))).toEqual([
  1486. {
  1487. type: HistoryEntryType.ORDER_STATE_TRANSITION,
  1488. data: {
  1489. from: 'Created',
  1490. to: 'AddingItems',
  1491. },
  1492. },
  1493. {
  1494. type: HistoryEntryType.ORDER_NOTE,
  1495. data: {
  1496. note: 'A public note',
  1497. },
  1498. },
  1499. ]);
  1500. });
  1501. it('update note', async () => {
  1502. const { updateOrderNote } = await adminClient.query<
  1503. UpdateOrderNote.Mutation,
  1504. UpdateOrderNote.Variables
  1505. >(UPDATE_ORDER_NOTE, {
  1506. input: {
  1507. noteId: firstNoteId,
  1508. note: 'An updated note',
  1509. },
  1510. });
  1511. expect(updateOrderNote.data).toEqual({
  1512. note: 'An updated note',
  1513. });
  1514. });
  1515. it('delete note', async () => {
  1516. const { order: before } = await adminClient.query<
  1517. GetOrderHistory.Query,
  1518. GetOrderHistory.Variables
  1519. >(GET_ORDER_HISTORY, { id: orderId });
  1520. expect(before?.history.totalItems).toBe(3);
  1521. const { deleteOrderNote } = await adminClient.query<
  1522. DeleteOrderNote.Mutation,
  1523. DeleteOrderNote.Variables
  1524. >(DELETE_ORDER_NOTE, {
  1525. id: firstNoteId,
  1526. });
  1527. expect(deleteOrderNote.result).toBe(DeletionResult.DELETED);
  1528. const { order: after } = await adminClient.query<
  1529. GetOrderHistory.Query,
  1530. GetOrderHistory.Variables
  1531. >(GET_ORDER_HISTORY, { id: orderId });
  1532. expect(after?.history.totalItems).toBe(2);
  1533. });
  1534. });
  1535. describe('issues', () => {
  1536. // https://github.com/vendure-ecommerce/vendure/issues/639
  1537. it('returns fulfillments for Order with no lines', async () => {
  1538. // Apply a coupon code just to create an active order with no OrderLines
  1539. await shopClient.query<ApplyCouponCode.Mutation, ApplyCouponCode.Variables>(APPLY_COUPON_CODE, {
  1540. couponCode: 'TEST',
  1541. });
  1542. const { activeOrder } = await shopClient.query<GetActiveOrder.Query>(GET_ACTIVE_ORDER);
  1543. const { order } = await adminClient.query<
  1544. GetOrderFulfillments.Query,
  1545. GetOrderFulfillments.Variables
  1546. >(GET_ORDER_FULFILLMENTS, {
  1547. id: activeOrder!.id,
  1548. });
  1549. expect(order?.fulfillments).toEqual([]);
  1550. });
  1551. // https://github.com/vendure-ecommerce/vendure/issues/603
  1552. it('orders correctly resolves quantities and OrderItems', async () => {
  1553. await shopClient.asAnonymousUser();
  1554. const { addItemToOrder } = await shopClient.query<
  1555. AddItemToOrder.Mutation,
  1556. AddItemToOrder.Variables
  1557. >(ADD_ITEM_TO_ORDER, {
  1558. productVariantId: 'T_1',
  1559. quantity: 2,
  1560. });
  1561. orderGuard.assertSuccess(addItemToOrder);
  1562. const { orders } = await adminClient.query<
  1563. GetOrderListWithQty.Query,
  1564. GetOrderListWithQty.Variables
  1565. >(GET_ORDERS_LIST_WITH_QUANTITIES, {
  1566. options: {
  1567. filter: {
  1568. code: { eq: addItemToOrder.code },
  1569. },
  1570. },
  1571. });
  1572. expect(orders.items[0].totalQuantity).toBe(2);
  1573. expect(orders.items[0].lines[0].quantity).toBe(2);
  1574. });
  1575. });
  1576. });
  1577. async function createTestOrder(
  1578. adminClient: SimpleGraphQLClient,
  1579. shopClient: SimpleGraphQLClient,
  1580. emailAddress: string,
  1581. password: string,
  1582. ): Promise<{
  1583. orderId: string;
  1584. product: GetProductWithVariants.Product;
  1585. productVariantId: string;
  1586. }> {
  1587. const result = await adminClient.query<GetProductWithVariants.Query, GetProductWithVariants.Variables>(
  1588. GET_PRODUCT_WITH_VARIANTS,
  1589. {
  1590. id: 'T_3',
  1591. },
  1592. );
  1593. const product = result.product!;
  1594. const productVariantId = product.variants[0].id;
  1595. // Set the ProductVariant to trackInventory
  1596. const { updateProductVariants } = await adminClient.query<
  1597. UpdateProductVariants.Mutation,
  1598. UpdateProductVariants.Variables
  1599. >(UPDATE_PRODUCT_VARIANTS, {
  1600. input: [
  1601. {
  1602. id: productVariantId,
  1603. trackInventory: GlobalFlag.TRUE,
  1604. },
  1605. ],
  1606. });
  1607. // Add the ProductVariant to the Order
  1608. await shopClient.asUserWithCredentials(emailAddress, password);
  1609. const { addItemToOrder } = await shopClient.query<AddItemToOrder.Mutation, AddItemToOrder.Variables>(
  1610. ADD_ITEM_TO_ORDER,
  1611. {
  1612. productVariantId,
  1613. quantity: 2,
  1614. },
  1615. );
  1616. const orderId = (addItemToOrder as UpdatedOrder.Fragment).id;
  1617. return { product, productVariantId, orderId };
  1618. }
  1619. async function getUnfulfilledOrderLineInput(
  1620. client: SimpleGraphQLClient,
  1621. id: string,
  1622. ): Promise<OrderLineInput[]> {
  1623. const { order } = await client.query<GetOrder.Query, GetOrder.Variables>(GET_ORDER, {
  1624. id,
  1625. });
  1626. const unfulfilledItems =
  1627. order?.lines.filter(l => {
  1628. const items = l.items.filter(i => i.fulfillment === null);
  1629. return items.length > 0 ? true : false;
  1630. }) || [];
  1631. return unfulfilledItems.map(l => ({
  1632. orderLineId: l.id,
  1633. quantity: l.items.length,
  1634. }));
  1635. }
  1636. export const GET_ORDER_LIST_FULFILLMENTS = gql`
  1637. query GetOrderListFulfillments {
  1638. orders {
  1639. items {
  1640. id
  1641. state
  1642. fulfillments {
  1643. id
  1644. state
  1645. nextStates
  1646. method
  1647. }
  1648. }
  1649. }
  1650. }
  1651. `;
  1652. export const GET_ORDER_FULFILLMENT_ITEMS = gql`
  1653. query GetOrderFulfillmentItems($id: ID!) {
  1654. order(id: $id) {
  1655. id
  1656. state
  1657. fulfillments {
  1658. ...Fulfillment
  1659. }
  1660. }
  1661. }
  1662. ${FULFILLMENT_FRAGMENT}
  1663. `;
  1664. const REFUND_FRAGMENT = gql`
  1665. fragment Refund on Refund {
  1666. id
  1667. state
  1668. items
  1669. transactionId
  1670. shipping
  1671. total
  1672. metadata
  1673. }
  1674. `;
  1675. export const REFUND_ORDER = gql`
  1676. mutation RefundOrder($input: RefundOrderInput!) {
  1677. refundOrder(input: $input) {
  1678. ...Refund
  1679. ... on ErrorResult {
  1680. errorCode
  1681. message
  1682. }
  1683. }
  1684. }
  1685. ${REFUND_FRAGMENT}
  1686. `;
  1687. export const SETTLE_REFUND = gql`
  1688. mutation SettleRefund($input: SettleRefundInput!) {
  1689. settleRefund(input: $input) {
  1690. ...Refund
  1691. ... on ErrorResult {
  1692. errorCode
  1693. message
  1694. }
  1695. }
  1696. }
  1697. ${REFUND_FRAGMENT}
  1698. `;
  1699. export const ADD_NOTE_TO_ORDER = gql`
  1700. mutation AddNoteToOrder($input: AddNoteToOrderInput!) {
  1701. addNoteToOrder(input: $input) {
  1702. id
  1703. }
  1704. }
  1705. `;
  1706. export const UPDATE_ORDER_NOTE = gql`
  1707. mutation UpdateOrderNote($input: UpdateOrderNoteInput!) {
  1708. updateOrderNote(input: $input) {
  1709. id
  1710. data
  1711. isPublic
  1712. }
  1713. }
  1714. `;
  1715. export const DELETE_ORDER_NOTE = gql`
  1716. mutation DeleteOrderNote($id: ID!) {
  1717. deleteOrderNote(id: $id) {
  1718. result
  1719. message
  1720. }
  1721. }
  1722. `;
  1723. const GET_ORDER_WITH_PAYMENTS = gql`
  1724. query GetOrderWithPayments($id: ID!) {
  1725. order(id: $id) {
  1726. id
  1727. payments {
  1728. id
  1729. errorMessage
  1730. metadata
  1731. }
  1732. }
  1733. }
  1734. `;
  1735. const GET_ORDERS_LIST_WITH_QUANTITIES = gql`
  1736. query GetOrderListWithQty($options: OrderListOptions) {
  1737. orders(options: $options) {
  1738. items {
  1739. id
  1740. code
  1741. totalQuantity
  1742. lines {
  1743. id
  1744. quantity
  1745. }
  1746. }
  1747. }
  1748. }
  1749. `;