order.e2e-spec.ts 71 KB

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