@rytass/wms-module-graphql 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -62,6 +62,22 @@ You register a **single** `GraphQLModule.forRootAsync(...)` of your own, mix you
62
62
 
63
63
  `WmsModuleGraphqlModule` internally wires `WmsModuleCoreModule.forRootAsync` so you do not need to import the core module separately when using this package.
64
64
 
65
+ ## Custom material entity
66
+
67
+ `materialEntity` is an ordinary `WMSBaseModuleOptions` field that reaches all the way down, so a downstream project's own item master can *be* the WMS material master — every resolver's `material` DataLoader resolves against it too:
68
+
69
+ ```ts
70
+ WmsModuleGraphqlModule.forRootAsync({
71
+ // a @ChildEntity() of MaterialEntityClass
72
+ useFactory: async () => ({ materialEntity: ItemMasterEntity }),
73
+ defaultLoaderSerialId: 'DEFAULT',
74
+ defaultLoaderTypeKey: 'DEFAULT',
75
+ defaultLocationKey: 'DEFAULT',
76
+ });
77
+ ```
78
+
79
+ Register the entity with TypeORM yourself (`TypeOrmModule.forFeature([ItemMasterEntity])`); the module checks at startup and refuses to boot if it is missing. Requires `@rytass/wms-base-nestjs-module` >= 0.3.0. See [`@rytass/wms-module-core`'s custom-entities guide](https://github.com/Rytass/WMS-Module/blob/main/libs/core/docs/custom-entities.md) for the single-table-inheritance caveats and which other entity options are not honoured yet.
80
+
65
81
  ## Authorization
66
82
 
67
83
  Every WMS root `@Query` / `@Mutation` is annotated with `@WmsOperation({ resource, action })` — a **host-neutral** permission requirement enforced by the library's own route-level `WmsAuthGuard`, which delegates the allow/deny decision to an `authChecker` **you** supply. The library never imports your auth system; you map WMS's `resource`/`action` onto whatever you use (Casbin, RBAC, JWT scopes…).
@@ -311,6 +311,12 @@ static forRootAsync(
311
311
  // defaultLocationKey: string — 預設儲存位置 key
312
312
  // + WmsAuthModuleOptions:
313
313
  // authChecker?: WmsAuthChecker — 選填;resolver 授權委派函式(見 Authorization)
314
+ //
315
+ // since 0.7.0(需 wms-base-nestjs-module >= 0.3.0):
316
+ // WMSBaseModuleOptions.materialEntity 會一路作用到所有 resolver 的
317
+ // material DataLoader。傳入 MaterialEntityClass 的 @ChildEntity() 即可,
318
+ // forRoot 與 async factory 皆支援;entity 需自行註冊給 TypeORM。
319
+ // 詳見 core 的 docs/custom-entities.md。
314
320
  ): DynamicModule
315
321
  ```
316
322
 
package/index.cjs.js CHANGED
@@ -55,6 +55,65 @@ exports.LoaderService = class LoaderService {
55
55
  const fields = ___default.default(Object.keys(__from)).sort();
56
56
  return `${__hash}_${fields.map((field) => data[field]).join('_')}`;
57
57
  }
58
+ /**
59
+ * resolve a `__from` property name to the physical column it is stored in.
60
+ *
61
+ * TypeORM's `FindOptionsWhere` only accepts real column properties. A
62
+ * `@RelationId()` property — e.g. `LocationEntityClass.parentId`, which mirrors
63
+ * the `parent` tree relation — is virtual and is rejected with
64
+ * `EntityPropertyNotFoundError`, even though it is backed by the relation's
65
+ * join column. Map those back to their join column so they stay batchable.
66
+ * @param EntityMetadata
67
+ * @param string
68
+ * @returns string | null
69
+ */
70
+ resolveColumnName(metadata, propertyName) {
71
+ const column = metadata.findColumnWithPropertyName(propertyName);
72
+ if (column)
73
+ return column.databaseName;
74
+ const relationId = metadata.relationIds.find((candidate) => candidate.propertyName === propertyName);
75
+ const joinColumns = relationId?.relation.joinColumns ?? [];
76
+ return joinColumns.length === 1 ? joinColumns[0].databaseName : null;
77
+ }
78
+ /**
79
+ * build the where clause for `__from` keys that TypeORM's find options cannot
80
+ * express (see {@link resolveColumnName}), by addressing the columns directly.
81
+ * @param SelectQueryBuilder
82
+ * @param EntityMetadata
83
+ * @param LoaderOptions[]
84
+ * @param (keyof T)[]
85
+ */
86
+ applyColumnWhere(qb, metadata, options, primaryKeys) {
87
+ const columns = primaryKeys.map((primaryKey) => {
88
+ const columnName = this.resolveColumnName(metadata, String(primaryKey));
89
+ if (!columnName)
90
+ throw new Error(`Loader cannot resolve property "${String(primaryKey)}" to a column of "${metadata.name}".`);
91
+ return {
92
+ primaryKey,
93
+ column: `entity.${this.dataSource.driver.escape(columnName)}`,
94
+ };
95
+ });
96
+ if (columns.length === 1) {
97
+ const [{ primaryKey, column }] = columns;
98
+ qb.andWhere(`${column} IN (:...wmsLoaderValues)`, {
99
+ wmsLoaderValues: options.map(({ __from }) => __from[primaryKey]),
100
+ });
101
+ return;
102
+ }
103
+ qb.andWhere(new typeorm.Brackets((where) => {
104
+ options.forEach(({ __from }, optionIndex) => {
105
+ const parameters = {};
106
+ const predicate = columns
107
+ .map(({ primaryKey, column }, columnIndex) => {
108
+ const parameter = `wmsLoaderValue_${optionIndex}_${columnIndex}`;
109
+ parameters[parameter] = __from[primaryKey];
110
+ return `${column} = :${parameter}`;
111
+ })
112
+ .join(' AND ');
113
+ where.orWhere(`(${predicate})`, parameters);
114
+ });
115
+ }));
116
+ }
58
117
  /**
59
118
  * query data by collecting primary keys from options with same `__entity`
60
119
  * @param LoaderOptions[]
@@ -65,7 +124,10 @@ exports.LoaderService = class LoaderService {
65
124
  const repo = this.dataSource.getRepository(__entity);
66
125
  let qb = repo.createQueryBuilder('entity');
67
126
  const primaryKeys = Object.keys(__from);
68
- if (primaryKeys.length > 1)
127
+ const hasVirtualKey = primaryKeys.some((primaryKey) => !repo.metadata.findColumnWithPropertyName(String(primaryKey)));
128
+ if (hasVirtualKey)
129
+ this.applyColumnWhere(qb, repo.metadata, options, primaryKeys);
130
+ else if (primaryKeys.length > 1)
69
131
  qb.setFindOptions({ where: options.map(({ __from }) => __from) });
70
132
  else {
71
133
  const [primaryKey] = primaryKeys;
@@ -3221,9 +3283,20 @@ Batches = tslib.__decorate([
3221
3283
  ], Batches);
3222
3284
 
3223
3285
  let StockResolver = class StockResolver {
3286
+ materialEntity;
3287
+ /**
3288
+ * Bound to the `MaterialEntity` Symbol, so the DataLoader queries whichever
3289
+ * material entity the consumer configured — the built-in `MaterialEntityClass`
3290
+ * unless `materialEntity` was passed to `forRoot`/`forRootAsync`. Loading the
3291
+ * built-in class outright would filter on its single-table-inheritance
3292
+ * discriminator and resolve nothing for a custom entity.
3293
+ */
3294
+ constructor(materialEntity) {
3295
+ this.materialEntity = materialEntity;
3296
+ }
3224
3297
  material({ materialId }, { loaders }) {
3225
3298
  return loaders.batchSingle.load({
3226
- __entity: wmsModuleCore.MaterialEntityClass,
3299
+ __entity: this.materialEntity,
3227
3300
  __from: { id: materialId },
3228
3301
  });
3229
3302
  }
@@ -3269,7 +3342,9 @@ tslib.__decorate([
3269
3342
  tslib.__metadata("design:returntype", Promise)
3270
3343
  ], StockResolver.prototype, "batch", null);
3271
3344
  StockResolver = tslib.__decorate([
3272
- graphql.Resolver(() => StockDto)
3345
+ graphql.Resolver(() => StockDto),
3346
+ tslib.__param(0, common.Inject(wmsModuleCore.MaterialEntity)),
3347
+ tslib.__metadata("design:paramtypes", [Function])
3273
3348
  ], StockResolver);
3274
3349
 
3275
3350
  var MapRangeType;
@@ -3611,8 +3686,21 @@ let LocationResolver = class LocationResolver {
3611
3686
  __from: { id: parentId },
3612
3687
  });
3613
3688
  }
3614
- children({ children }) {
3615
- return children;
3689
+ /**
3690
+ * `children` must never be read off the parent entity: only
3691
+ * `LocationQuery.location` and `LocationService.findMany` eager-load the tree
3692
+ * relation. Every other producer of a `LocationDto` (stock/loader/change-log/
3693
+ * transfer/ship/receive/warehouse-map resolvers, the `parent` field above and
3694
+ * the create/update mutations) hands back an entity whose `children` is
3695
+ * `undefined`, which breaks the non-nullable list. Always resolve through the
3696
+ * request-scoped loader instead.
3697
+ */
3698
+ children({ id }, { loaders }) {
3699
+ return loaders.batchMany.load({
3700
+ __entity: wmsModuleCore.LocationEntityClass,
3701
+ __from: { parentId: id },
3702
+ __conditions: (qb) => qb.orderBy('entity."key"', 'ASC'),
3703
+ });
3616
3704
  }
3617
3705
  };
3618
3706
  tslib.__decorate([
@@ -3634,9 +3722,10 @@ tslib.__decorate([
3634
3722
  tslib.__decorate([
3635
3723
  graphql.ResolveField(() => [LocationDto]),
3636
3724
  tslib.__param(0, graphql.Parent()),
3725
+ tslib.__param(1, graphql.Context()),
3637
3726
  tslib.__metadata("design:type", Function),
3638
- tslib.__metadata("design:paramtypes", [wmsModuleCore.LocationEntityClass]),
3639
- tslib.__metadata("design:returntype", void 0)
3727
+ tslib.__metadata("design:paramtypes", [wmsModuleCore.LocationEntityClass, Object]),
3728
+ tslib.__metadata("design:returntype", Promise)
3640
3729
  ], LocationResolver.prototype, "children", null);
3641
3730
  LocationResolver = tslib.__decorate([
3642
3731
  graphql.Resolver(() => LocationDto)
@@ -3829,15 +3918,25 @@ UnitQuery = tslib.__decorate([
3829
3918
 
3830
3919
  let AggregatedStockResolver = class AggregatedStockResolver {
3831
3920
  stockService;
3832
- constructor(stockService) {
3921
+ materialEntity;
3922
+ /**
3923
+ * `materialEntity` is bound to the `MaterialEntity` Symbol, so the DataLoader
3924
+ * queries whichever material entity the consumer configured — the built-in
3925
+ * `MaterialEntityClass` unless `materialEntity` was passed to
3926
+ * `forRoot`/`forRootAsync`. Loading the built-in class outright would filter on
3927
+ * its single-table-inheritance discriminator and resolve nothing for a custom
3928
+ * entity.
3929
+ */
3930
+ constructor(stockService, materialEntity) {
3833
3931
  this.stockService = stockService;
3932
+ this.materialEntity = materialEntity;
3834
3933
  }
3835
3934
  async id(stock) {
3836
3935
  return this.stockService.generatedAggregateKey(stock);
3837
3936
  }
3838
3937
  async material({ materialId }, { loaders }) {
3839
3938
  return loaders.batchSingle.load({
3840
- __entity: wmsModuleCore.MaterialEntityClass,
3939
+ __entity: this.materialEntity,
3841
3940
  __from: { id: materialId },
3842
3941
  });
3843
3942
  }
@@ -3905,7 +4004,8 @@ tslib.__decorate([
3905
4004
  ], AggregatedStockResolver.prototype, "loader", null);
3906
4005
  AggregatedStockResolver = tslib.__decorate([
3907
4006
  graphql.Resolver(() => AggregatedStockDto),
3908
- tslib.__metadata("design:paramtypes", [wmsModuleCore.StockService])
4007
+ tslib.__param(1, common.Inject(wmsModuleCore.MaterialEntity)),
4008
+ tslib.__metadata("design:paramtypes", [wmsModuleCore.StockService, Function])
3909
4009
  ], AggregatedStockResolver);
3910
4010
 
3911
4011
  let CreateTransferOrderItemArgs = class CreateTransferOrderItemArgs {
@@ -4291,9 +4391,20 @@ TransferOrderResolver = tslib.__decorate([
4291
4391
  graphql.Resolver(() => TransferOrderDto)
4292
4392
  ], TransferOrderResolver);
4293
4393
  let TransferOrderItemResolver = class TransferOrderItemResolver {
4394
+ materialEntity;
4395
+ /**
4396
+ * Bound to the `MaterialEntity` Symbol, so the DataLoader queries whichever
4397
+ * material entity the consumer configured — the built-in `MaterialEntityClass`
4398
+ * unless `materialEntity` was passed to `forRoot`/`forRootAsync`. Loading the
4399
+ * built-in class outright would filter on its single-table-inheritance
4400
+ * discriminator and resolve nothing for a custom entity.
4401
+ */
4402
+ constructor(materialEntity) {
4403
+ this.materialEntity = materialEntity;
4404
+ }
4294
4405
  material({ materialId }, { loaders }) {
4295
4406
  return loaders.batchSingle.load({
4296
- __entity: wmsModuleCore.MaterialEntityClass,
4407
+ __entity: this.materialEntity,
4297
4408
  __from: { id: materialId },
4298
4409
  });
4299
4410
  }
@@ -4413,7 +4524,9 @@ tslib.__decorate([
4413
4524
  tslib.__metadata("design:returntype", Promise)
4414
4525
  ], TransferOrderItemResolver.prototype, "transferredQuantity", null);
4415
4526
  TransferOrderItemResolver = tslib.__decorate([
4416
- graphql.Resolver(() => TransferOrderItemDto)
4527
+ graphql.Resolver(() => TransferOrderItemDto),
4528
+ tslib.__param(0, common.Inject(wmsModuleCore.MaterialEntity)),
4529
+ tslib.__metadata("design:paramtypes", [Function])
4417
4530
  ], TransferOrderItemResolver);
4418
4531
 
4419
4532
  let CreateHoldInventoryOrderItemArgs = class CreateHoldInventoryOrderItemArgs {
@@ -4709,9 +4822,20 @@ BatchQuery = tslib.__decorate([
4709
4822
  ], BatchQuery);
4710
4823
 
4711
4824
  let ChangeLogResolver = class ChangeLogResolver {
4825
+ materialEntity;
4826
+ /**
4827
+ * Bound to the `MaterialEntity` Symbol, so the DataLoader queries whichever
4828
+ * material entity the consumer configured — the built-in `MaterialEntityClass`
4829
+ * unless `materialEntity` was passed to `forRoot`/`forRootAsync`. Loading the
4830
+ * built-in class outright would filter on its single-table-inheritance
4831
+ * discriminator and resolve nothing for a custom entity.
4832
+ */
4833
+ constructor(materialEntity) {
4834
+ this.materialEntity = materialEntity;
4835
+ }
4712
4836
  material({ materialId }, { loaders }) {
4713
4837
  return loaders.batchSingle.load({
4714
- __entity: wmsModuleCore.MaterialEntityClass,
4838
+ __entity: this.materialEntity,
4715
4839
  __from: { id: materialId },
4716
4840
  });
4717
4841
  }
@@ -4757,7 +4881,9 @@ tslib.__decorate([
4757
4881
  tslib.__metadata("design:returntype", Promise)
4758
4882
  ], ChangeLogResolver.prototype, "batch", null);
4759
4883
  ChangeLogResolver = tslib.__decorate([
4760
- graphql.Resolver(() => ChangeLogDto)
4884
+ graphql.Resolver(() => ChangeLogDto),
4885
+ tslib.__param(0, common.Inject(wmsModuleCore.MaterialEntity)),
4886
+ tslib.__metadata("design:paramtypes", [Function])
4761
4887
  ], ChangeLogResolver);
4762
4888
 
4763
4889
  let ReceiveInventoryOrderResolver = class ReceiveInventoryOrderResolver {
@@ -4781,9 +4907,20 @@ ReceiveInventoryOrderResolver = tslib.__decorate([
4781
4907
  graphql.Resolver(() => ReceiveInventoryOrderDto)
4782
4908
  ], ReceiveInventoryOrderResolver);
4783
4909
  let ReceiveInventoryOrderItemResolver = class ReceiveInventoryOrderItemResolver {
4910
+ materialEntity;
4911
+ /**
4912
+ * Bound to the `MaterialEntity` Symbol, so the DataLoader queries whichever
4913
+ * material entity the consumer configured — the built-in `MaterialEntityClass`
4914
+ * unless `materialEntity` was passed to `forRoot`/`forRootAsync`. Loading the
4915
+ * built-in class outright would filter on its single-table-inheritance
4916
+ * discriminator and resolve nothing for a custom entity.
4917
+ */
4918
+ constructor(materialEntity) {
4919
+ this.materialEntity = materialEntity;
4920
+ }
4784
4921
  material({ materialId }, { loaders }) {
4785
4922
  return loaders.batchSingle.load({
4786
- __entity: wmsModuleCore.MaterialEntityClass,
4923
+ __entity: this.materialEntity,
4787
4924
  __from: { id: materialId },
4788
4925
  });
4789
4926
  }
@@ -4866,7 +5003,9 @@ tslib.__decorate([
4866
5003
  tslib.__metadata("design:returntype", Promise)
4867
5004
  ], ReceiveInventoryOrderItemResolver.prototype, "receivedQuantity", null);
4868
5005
  ReceiveInventoryOrderItemResolver = tslib.__decorate([
4869
- graphql.Resolver(() => ReceiveInventoryOrderItemDto)
5006
+ graphql.Resolver(() => ReceiveInventoryOrderItemDto),
5007
+ tslib.__param(0, common.Inject(wmsModuleCore.MaterialEntity)),
5008
+ tslib.__metadata("design:paramtypes", [Function])
4870
5009
  ], ReceiveInventoryOrderItemResolver);
4871
5010
 
4872
5011
  let FindAllocateInventoryOrderArgs = class FindAllocateInventoryOrderArgs extends PaginationArgs {
@@ -5203,9 +5342,20 @@ ShipInventoryOrderResolver = tslib.__decorate([
5203
5342
  graphql.Resolver(() => ShipInventoryOrderDto)
5204
5343
  ], ShipInventoryOrderResolver);
5205
5344
  let ShipInventoryOrderItemResolver = class ShipInventoryOrderItemResolver {
5345
+ materialEntity;
5346
+ /**
5347
+ * Bound to the `MaterialEntity` Symbol, so the DataLoader queries whichever
5348
+ * material entity the consumer configured — the built-in `MaterialEntityClass`
5349
+ * unless `materialEntity` was passed to `forRoot`/`forRootAsync`. Loading the
5350
+ * built-in class outright would filter on its single-table-inheritance
5351
+ * discriminator and resolve nothing for a custom entity.
5352
+ */
5353
+ constructor(materialEntity) {
5354
+ this.materialEntity = materialEntity;
5355
+ }
5206
5356
  material({ materialId }, { loaders }) {
5207
5357
  return loaders.batchSingle.load({
5208
- __entity: wmsModuleCore.MaterialEntityClass,
5358
+ __entity: this.materialEntity,
5209
5359
  __from: { id: materialId },
5210
5360
  });
5211
5361
  }
@@ -5333,9 +5483,22 @@ tslib.__decorate([
5333
5483
  tslib.__metadata("design:returntype", Promise)
5334
5484
  ], ShipInventoryOrderItemResolver.prototype, "shippedQuantity", null);
5335
5485
  ShipInventoryOrderItemResolver = tslib.__decorate([
5336
- graphql.Resolver(() => ShipInventoryOrderItemDto)
5486
+ graphql.Resolver(() => ShipInventoryOrderItemDto),
5487
+ tslib.__param(0, common.Inject(wmsModuleCore.MaterialEntity)),
5488
+ tslib.__metadata("design:paramtypes", [Function])
5337
5489
  ], ShipInventoryOrderItemResolver);
5338
5490
  let ShipInventoryOrderReferenceResolver = class ShipInventoryOrderReferenceResolver {
5491
+ materialEntity;
5492
+ /**
5493
+ * Bound to the `MaterialEntity` Symbol, so the DataLoader queries whichever
5494
+ * material entity the consumer configured — the built-in `MaterialEntityClass`
5495
+ * unless `materialEntity` was passed to `forRoot`/`forRootAsync`. Loading the
5496
+ * built-in class outright would filter on its single-table-inheritance
5497
+ * discriminator and resolve nothing for a custom entity.
5498
+ */
5499
+ constructor(materialEntity) {
5500
+ this.materialEntity = materialEntity;
5501
+ }
5339
5502
  async shippedQuantity({ orderId, serialId }, { loaders }) {
5340
5503
  const items = await loaders.batchMany.load({
5341
5504
  __entity: wmsModuleCore.ShipInventoryOrderItemEntityClass,
@@ -5350,7 +5513,7 @@ let ShipInventoryOrderReferenceResolver = class ShipInventoryOrderReferenceResol
5350
5513
  }
5351
5514
  material({ materialId }, { loaders }) {
5352
5515
  return loaders.batchSingle.load({
5353
- __entity: wmsModuleCore.MaterialEntityClass,
5516
+ __entity: this.materialEntity,
5354
5517
  __from: { id: materialId },
5355
5518
  });
5356
5519
  }
@@ -5406,7 +5569,9 @@ tslib.__decorate([
5406
5569
  tslib.__metadata("design:returntype", Promise)
5407
5570
  ], ShipInventoryOrderReferenceResolver.prototype, "items", null);
5408
5571
  ShipInventoryOrderReferenceResolver = tslib.__decorate([
5409
- graphql.Resolver(() => ShipInventoryOrderReferenceDto)
5572
+ graphql.Resolver(() => ShipInventoryOrderReferenceDto),
5573
+ tslib.__param(0, common.Inject(wmsModuleCore.MaterialEntity)),
5574
+ tslib.__metadata("design:paramtypes", [Function])
5410
5575
  ], ShipInventoryOrderReferenceResolver);
5411
5576
 
5412
5577
  let FindSaleOrderArgs = class FindSaleOrderArgs extends PaginationArgs {
@@ -1,7 +1,13 @@
1
- import { DataSource, FindOptionsWhere, ObjectLiteral, SelectQueryBuilder } from 'typeorm';
1
+ import { DataSource, EntityTarget, FindOptionsWhere, ObjectLiteral, SelectQueryBuilder } from 'typeorm';
2
2
  import DataLoader from 'dataloader';
3
3
  export interface LoaderOptions<K> {
4
- __entity: ObjectLiteral;
4
+ /**
5
+ * Widened from `ObjectLiteral` so a resolver can pass the entity it had
6
+ * injected from the `MaterialEntity` / `…Entity` Symbols — those are typed as
7
+ * `EntityTarget`, which is also exactly what `core()` hands to
8
+ * `getRepository()`.
9
+ */
10
+ __entity: EntityTarget<ObjectLiteral>;
5
11
  __from: FindOptionsWhere<K>;
6
12
  __conditions?: (qb: SelectQueryBuilder<ObjectLiteral>) => SelectQueryBuilder<ObjectLiteral>;
7
13
  __seed?: string;
@@ -33,6 +39,28 @@ export declare class LoaderService {
33
39
  * @returns Map<string, data>
34
40
  */
35
41
  private useIndex;
42
+ /**
43
+ * resolve a `__from` property name to the physical column it is stored in.
44
+ *
45
+ * TypeORM's `FindOptionsWhere` only accepts real column properties. A
46
+ * `@RelationId()` property — e.g. `LocationEntityClass.parentId`, which mirrors
47
+ * the `parent` tree relation — is virtual and is rejected with
48
+ * `EntityPropertyNotFoundError`, even though it is backed by the relation's
49
+ * join column. Map those back to their join column so they stay batchable.
50
+ * @param EntityMetadata
51
+ * @param string
52
+ * @returns string | null
53
+ */
54
+ private resolveColumnName;
55
+ /**
56
+ * build the where clause for `__from` keys that TypeORM's find options cannot
57
+ * express (see {@link resolveColumnName}), by addressing the columns directly.
58
+ * @param SelectQueryBuilder
59
+ * @param EntityMetadata
60
+ * @param LoaderOptions[]
61
+ * @param (keyof T)[]
62
+ */
63
+ private applyColumnWhere;
36
64
  /**
37
65
  * query data by collecting primary keys from options with same `__entity`
38
66
  * @param LoaderOptions[]
@@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common';
3
3
  import { createHash } from 'crypto';
4
4
  import _ from 'lodash';
5
5
  import { LRUCache } from 'lru-cache';
6
- import { DataSource, In } from 'typeorm';
6
+ import { DataSource, Brackets, In } from 'typeorm';
7
7
  import DataLoader from 'dataloader';
8
8
 
9
9
  let LoaderService = class LoaderService {
@@ -42,6 +42,65 @@ let LoaderService = class LoaderService {
42
42
  const fields = _(Object.keys(__from)).sort();
43
43
  return `${__hash}_${fields.map((field) => data[field]).join('_')}`;
44
44
  }
45
+ /**
46
+ * resolve a `__from` property name to the physical column it is stored in.
47
+ *
48
+ * TypeORM's `FindOptionsWhere` only accepts real column properties. A
49
+ * `@RelationId()` property — e.g. `LocationEntityClass.parentId`, which mirrors
50
+ * the `parent` tree relation — is virtual and is rejected with
51
+ * `EntityPropertyNotFoundError`, even though it is backed by the relation's
52
+ * join column. Map those back to their join column so they stay batchable.
53
+ * @param EntityMetadata
54
+ * @param string
55
+ * @returns string | null
56
+ */
57
+ resolveColumnName(metadata, propertyName) {
58
+ const column = metadata.findColumnWithPropertyName(propertyName);
59
+ if (column)
60
+ return column.databaseName;
61
+ const relationId = metadata.relationIds.find((candidate) => candidate.propertyName === propertyName);
62
+ const joinColumns = relationId?.relation.joinColumns ?? [];
63
+ return joinColumns.length === 1 ? joinColumns[0].databaseName : null;
64
+ }
65
+ /**
66
+ * build the where clause for `__from` keys that TypeORM's find options cannot
67
+ * express (see {@link resolveColumnName}), by addressing the columns directly.
68
+ * @param SelectQueryBuilder
69
+ * @param EntityMetadata
70
+ * @param LoaderOptions[]
71
+ * @param (keyof T)[]
72
+ */
73
+ applyColumnWhere(qb, metadata, options, primaryKeys) {
74
+ const columns = primaryKeys.map((primaryKey) => {
75
+ const columnName = this.resolveColumnName(metadata, String(primaryKey));
76
+ if (!columnName)
77
+ throw new Error(`Loader cannot resolve property "${String(primaryKey)}" to a column of "${metadata.name}".`);
78
+ return {
79
+ primaryKey,
80
+ column: `entity.${this.dataSource.driver.escape(columnName)}`,
81
+ };
82
+ });
83
+ if (columns.length === 1) {
84
+ const [{ primaryKey, column }] = columns;
85
+ qb.andWhere(`${column} IN (:...wmsLoaderValues)`, {
86
+ wmsLoaderValues: options.map(({ __from }) => __from[primaryKey]),
87
+ });
88
+ return;
89
+ }
90
+ qb.andWhere(new Brackets((where) => {
91
+ options.forEach(({ __from }, optionIndex) => {
92
+ const parameters = {};
93
+ const predicate = columns
94
+ .map(({ primaryKey, column }, columnIndex) => {
95
+ const parameter = `wmsLoaderValue_${optionIndex}_${columnIndex}`;
96
+ parameters[parameter] = __from[primaryKey];
97
+ return `${column} = :${parameter}`;
98
+ })
99
+ .join(' AND ');
100
+ where.orWhere(`(${predicate})`, parameters);
101
+ });
102
+ }));
103
+ }
45
104
  /**
46
105
  * query data by collecting primary keys from options with same `__entity`
47
106
  * @param LoaderOptions[]
@@ -52,7 +111,10 @@ let LoaderService = class LoaderService {
52
111
  const repo = this.dataSource.getRepository(__entity);
53
112
  let qb = repo.createQueryBuilder('entity');
54
113
  const primaryKeys = Object.keys(__from);
55
- if (primaryKeys.length > 1)
114
+ const hasVirtualKey = primaryKeys.some((primaryKey) => !repo.metadata.findColumnWithPropertyName(String(primaryKey)));
115
+ if (hasVirtualKey)
116
+ this.applyColumnWhere(qb, repo.metadata, options, primaryKeys);
117
+ else if (primaryKeys.length > 1)
56
118
  qb.setFindOptions({ where: options.map(({ __from }) => __from) });
57
119
  else {
58
120
  const [primaryKey] = primaryKeys;
@@ -5,7 +5,16 @@ import { BatchDto } from '../dto/batch.dto';
5
5
  import { LocationDto } from '../dto/location.dto';
6
6
  export declare class AggregatedStockResolver {
7
7
  private readonly stockService;
8
- constructor(stockService: StockService);
8
+ private readonly materialEntity;
9
+ /**
10
+ * `materialEntity` is bound to the `MaterialEntity` Symbol, so the DataLoader
11
+ * queries whichever material entity the consumer configured — the built-in
12
+ * `MaterialEntityClass` unless `materialEntity` was passed to
13
+ * `forRoot`/`forRootAsync`. Loading the built-in class outright would filter on
14
+ * its single-table-inheritance discriminator and resolve nothing for a custom
15
+ * entity.
16
+ */
17
+ constructor(stockService: StockService, materialEntity: new () => MaterialEntityClass);
9
18
  id(stock: AggregatedStockDto): Promise<string>;
10
19
  material({ materialId }: AggregatedStockDto, { loaders }: LoaderType<MaterialEntityClass>): Promise<MaterialDto>;
11
20
  batch({ batchId }: AggregatedStockDto, { loaders }: LoaderType<BatchEntityClass>): Promise<BatchDto>;
@@ -1,4 +1,5 @@
1
1
  import { __decorate, __param, __metadata } from 'tslib';
2
+ import { Inject } from '@nestjs/common';
2
3
  import { ResolveField, ID, Parent, Context, Resolver } from '@nestjs/graphql';
3
4
  import '../dto/loader-type.dto.js';
4
5
  import { LoaderDto } from '../dto/loader.dto.js';
@@ -24,21 +25,31 @@ import '../dto/scrape-order.dto.js';
24
25
  import '../dto/scrape-order-item.dto.js';
25
26
  import '../dto/quality-inspection-order.dto.js';
26
27
  import '../dto/quality-inspection-order-item.dto.js';
27
- import { StockService, MaterialEntityClass, BatchEntityClass, BatchGroupEntityClass, LocationEntityClass, LoadersEntityClass } from '@rytass/wms-module-core';
28
+ import { MaterialEntity, StockService, BatchEntityClass, BatchGroupEntityClass, LocationEntityClass, LoadersEntityClass } from '@rytass/wms-module-core';
28
29
  import { BatchDto } from '../dto/batch.dto.js';
29
30
  import { LocationDto } from '../dto/location.dto.js';
30
31
 
31
32
  let AggregatedStockResolver = class AggregatedStockResolver {
32
33
  stockService;
33
- constructor(stockService) {
34
+ materialEntity;
35
+ /**
36
+ * `materialEntity` is bound to the `MaterialEntity` Symbol, so the DataLoader
37
+ * queries whichever material entity the consumer configured — the built-in
38
+ * `MaterialEntityClass` unless `materialEntity` was passed to
39
+ * `forRoot`/`forRootAsync`. Loading the built-in class outright would filter on
40
+ * its single-table-inheritance discriminator and resolve nothing for a custom
41
+ * entity.
42
+ */
43
+ constructor(stockService, materialEntity) {
34
44
  this.stockService = stockService;
45
+ this.materialEntity = materialEntity;
35
46
  }
36
47
  async id(stock) {
37
48
  return this.stockService.generatedAggregateKey(stock);
38
49
  }
39
50
  async material({ materialId }, { loaders }) {
40
51
  return loaders.batchSingle.load({
41
- __entity: MaterialEntityClass,
52
+ __entity: this.materialEntity,
42
53
  __from: { id: materialId },
43
54
  });
44
55
  }
@@ -106,7 +117,8 @@ __decorate([
106
117
  ], AggregatedStockResolver.prototype, "loader", null);
107
118
  AggregatedStockResolver = __decorate([
108
119
  Resolver(() => AggregatedStockDto),
109
- __metadata("design:paramtypes", [StockService])
120
+ __param(1, Inject(MaterialEntity)),
121
+ __metadata("design:paramtypes", [StockService, Function])
110
122
  ], AggregatedStockResolver);
111
123
 
112
124
  export { AggregatedStockResolver };
@@ -4,6 +4,15 @@ import { type LoaderType } from '../helpers';
4
4
  import { LocationDto } from '../dto/location.dto';
5
5
  import { BatchDto } from '../dto/batch.dto';
6
6
  export declare class ChangeLogResolver {
7
+ private readonly materialEntity;
8
+ /**
9
+ * Bound to the `MaterialEntity` Symbol, so the DataLoader queries whichever
10
+ * material entity the consumer configured — the built-in `MaterialEntityClass`
11
+ * unless `materialEntity` was passed to `forRoot`/`forRootAsync`. Loading the
12
+ * built-in class outright would filter on its single-table-inheritance
13
+ * discriminator and resolve nothing for a custom entity.
14
+ */
15
+ constructor(materialEntity: new () => MaterialEntityClass);
7
16
  material({ materialId }: StockEntityClass, { loaders }: LoaderType<MaterialEntityClass>): Promise<MaterialDto>;
8
17
  location({ locationId }: StockEntityClass, { loaders }: LoaderType<LocationEntityClass>): Promise<LocationDto>;
9
18
  batch({ batchId }: StockEntityClass, { loaders }: LoaderType<BatchEntityClass>): Promise<BatchDto>;
@@ -1,4 +1,5 @@
1
1
  import { __decorate, __param, __metadata } from 'tslib';
2
+ import { Inject } from '@nestjs/common';
2
3
  import { ResolveField, Parent, Context, Resolver } from '@nestjs/graphql';
3
4
  import '../dto/loader-type.dto.js';
4
5
  import '../dto/loader.dto.js';
@@ -24,14 +25,25 @@ import '../dto/scrape-order.dto.js';
24
25
  import '../dto/scrape-order-item.dto.js';
25
26
  import '../dto/quality-inspection-order.dto.js';
26
27
  import '../dto/quality-inspection-order-item.dto.js';
27
- import { StockEntityClass, MaterialEntityClass, LocationEntityClass, BatchEntityClass, BatchGroupEntityClass } from '@rytass/wms-module-core';
28
+ import { StockEntityClass, MaterialEntity, LocationEntityClass, BatchEntityClass, BatchGroupEntityClass } from '@rytass/wms-module-core';
28
29
  import { LocationDto } from '../dto/location.dto.js';
29
30
  import { BatchDto } from '../dto/batch.dto.js';
30
31
 
31
32
  let ChangeLogResolver = class ChangeLogResolver {
33
+ materialEntity;
34
+ /**
35
+ * Bound to the `MaterialEntity` Symbol, so the DataLoader queries whichever
36
+ * material entity the consumer configured — the built-in `MaterialEntityClass`
37
+ * unless `materialEntity` was passed to `forRoot`/`forRootAsync`. Loading the
38
+ * built-in class outright would filter on its single-table-inheritance
39
+ * discriminator and resolve nothing for a custom entity.
40
+ */
41
+ constructor(materialEntity) {
42
+ this.materialEntity = materialEntity;
43
+ }
32
44
  material({ materialId }, { loaders }) {
33
45
  return loaders.batchSingle.load({
34
- __entity: MaterialEntityClass,
46
+ __entity: this.materialEntity,
35
47
  __from: { id: materialId },
36
48
  });
37
49
  }
@@ -77,7 +89,9 @@ __decorate([
77
89
  __metadata("design:returntype", Promise)
78
90
  ], ChangeLogResolver.prototype, "batch", null);
79
91
  ChangeLogResolver = __decorate([
80
- Resolver(() => ChangeLogDto)
92
+ Resolver(() => ChangeLogDto),
93
+ __param(0, Inject(MaterialEntity)),
94
+ __metadata("design:paramtypes", [Function])
81
95
  ], ChangeLogResolver);
82
96
 
83
97
  export { ChangeLogResolver };
@@ -6,5 +6,14 @@ import { LocationEntityClass } from '@rytass/wms-module-core';
6
6
  export declare class LocationResolver {
7
7
  mapData({ id }: LocationDto, { loaders }: LoaderType<WarehouseMapEntity>): Promise<MapDataDto | null>;
8
8
  parent({ parentId }: LocationEntityClass, { loaders }: LoaderType<LocationEntityClass>): Promise<LocationEntityClass> | null;
9
- children({ children }: LocationEntityClass): import("@rytass/wms-base-nestjs-module").LocationEntity[];
9
+ /**
10
+ * `children` must never be read off the parent entity: only
11
+ * `LocationQuery.location` and `LocationService.findMany` eager-load the tree
12
+ * relation. Every other producer of a `LocationDto` (stock/loader/change-log/
13
+ * transfer/ship/receive/warehouse-map resolvers, the `parent` field above and
14
+ * the create/update mutations) hands back an entity whose `children` is
15
+ * `undefined`, which breaks the non-nullable list. Always resolve through the
16
+ * request-scoped loader instead.
17
+ */
18
+ children({ id }: LocationEntityClass, { loaders }: LoaderType<LocationEntityClass>): Promise<LocationDto[]>;
10
19
  }
@@ -21,8 +21,21 @@ let LocationResolver = class LocationResolver {
21
21
  __from: { id: parentId },
22
22
  });
23
23
  }
24
- children({ children }) {
25
- return children;
24
+ /**
25
+ * `children` must never be read off the parent entity: only
26
+ * `LocationQuery.location` and `LocationService.findMany` eager-load the tree
27
+ * relation. Every other producer of a `LocationDto` (stock/loader/change-log/
28
+ * transfer/ship/receive/warehouse-map resolvers, the `parent` field above and
29
+ * the create/update mutations) hands back an entity whose `children` is
30
+ * `undefined`, which breaks the non-nullable list. Always resolve through the
31
+ * request-scoped loader instead.
32
+ */
33
+ children({ id }, { loaders }) {
34
+ return loaders.batchMany.load({
35
+ __entity: LocationEntityClass,
36
+ __from: { parentId: id },
37
+ __conditions: (qb) => qb.orderBy('entity."key"', 'ASC'),
38
+ });
26
39
  }
27
40
  };
28
41
  __decorate([
@@ -44,9 +57,10 @@ __decorate([
44
57
  __decorate([
45
58
  ResolveField(() => [LocationDto]),
46
59
  __param(0, Parent()),
60
+ __param(1, Context()),
47
61
  __metadata("design:type", Function),
48
- __metadata("design:paramtypes", [LocationEntityClass]),
49
- __metadata("design:returntype", void 0)
62
+ __metadata("design:paramtypes", [LocationEntityClass, Object]),
63
+ __metadata("design:returntype", Promise)
50
64
  ], LocationResolver.prototype, "children", null);
51
65
  LocationResolver = __decorate([
52
66
  Resolver(() => LocationDto)
@@ -7,6 +7,15 @@ export declare class ReceiveInventoryOrderResolver {
7
7
  items({ id }: ReceiveInventoryOrderDto, { loaders }: LoaderType<ReceiveInventoryOrderItemEntityClass>): Promise<ReceiveInventoryOrderItemDto[]>;
8
8
  }
9
9
  export declare class ReceiveInventoryOrderItemResolver {
10
+ private readonly materialEntity;
11
+ /**
12
+ * Bound to the `MaterialEntity` Symbol, so the DataLoader queries whichever
13
+ * material entity the consumer configured — the built-in `MaterialEntityClass`
14
+ * unless `materialEntity` was passed to `forRoot`/`forRootAsync`. Loading the
15
+ * built-in class outright would filter on its single-table-inheritance
16
+ * discriminator and resolve nothing for a custom entity.
17
+ */
18
+ constructor(materialEntity: new () => MaterialEntityClass);
10
19
  material({ materialId }: ReceiveInventoryOrderItemEntityClass, { loaders }: LoaderType<MaterialEntityClass>): Promise<MaterialDto>;
11
20
  batch({ batchId }: ReceiveInventoryOrderItemEntityClass, { loaders }: LoaderType<BatchEntityClass>): Promise<BatchDto | null>;
12
21
  location({ locationId }: ReceiveInventoryOrderItemEntityClass, { loaders }: LoaderType<LocationEntityClass>): Promise<LocationDto | null>;
@@ -1,4 +1,5 @@
1
1
  import { __decorate, __param, __metadata } from 'tslib';
2
+ import { Inject } from '@nestjs/common';
2
3
  import { ResolveField, Parent, Context, Resolver, Int } from '@nestjs/graphql';
3
4
  import '../dto/loader-type.dto.js';
4
5
  import { LoaderDto } from '../dto/loader.dto.js';
@@ -24,7 +25,7 @@ import '../dto/scrape-order.dto.js';
24
25
  import '../dto/scrape-order-item.dto.js';
25
26
  import '../dto/quality-inspection-order.dto.js';
26
27
  import '../dto/quality-inspection-order-item.dto.js';
27
- import { ReceiveInventoryOrderItemEntityClass, MaterialEntityClass, BatchEntityClass, BatchGroupEntityClass, LocationEntityClass, LoadersEntityClass, ReceiveInventoryOrderChangeEntityClass } from '@rytass/wms-module-core';
28
+ import { ReceiveInventoryOrderItemEntityClass, MaterialEntity, BatchEntityClass, BatchGroupEntityClass, LocationEntityClass, LoadersEntityClass, ReceiveInventoryOrderChangeEntityClass } from '@rytass/wms-module-core';
28
29
  import { BatchDto } from '../dto/batch.dto.js';
29
30
  import { LocationDto } from '../dto/location.dto.js';
30
31
 
@@ -49,9 +50,20 @@ ReceiveInventoryOrderResolver = __decorate([
49
50
  Resolver(() => ReceiveInventoryOrderDto)
50
51
  ], ReceiveInventoryOrderResolver);
51
52
  let ReceiveInventoryOrderItemResolver = class ReceiveInventoryOrderItemResolver {
53
+ materialEntity;
54
+ /**
55
+ * Bound to the `MaterialEntity` Symbol, so the DataLoader queries whichever
56
+ * material entity the consumer configured — the built-in `MaterialEntityClass`
57
+ * unless `materialEntity` was passed to `forRoot`/`forRootAsync`. Loading the
58
+ * built-in class outright would filter on its single-table-inheritance
59
+ * discriminator and resolve nothing for a custom entity.
60
+ */
61
+ constructor(materialEntity) {
62
+ this.materialEntity = materialEntity;
63
+ }
52
64
  material({ materialId }, { loaders }) {
53
65
  return loaders.batchSingle.load({
54
- __entity: MaterialEntityClass,
66
+ __entity: this.materialEntity,
55
67
  __from: { id: materialId },
56
68
  });
57
69
  }
@@ -134,7 +146,9 @@ __decorate([
134
146
  __metadata("design:returntype", Promise)
135
147
  ], ReceiveInventoryOrderItemResolver.prototype, "receivedQuantity", null);
136
148
  ReceiveInventoryOrderItemResolver = __decorate([
137
- Resolver(() => ReceiveInventoryOrderItemDto)
149
+ Resolver(() => ReceiveInventoryOrderItemDto),
150
+ __param(0, Inject(MaterialEntity)),
151
+ __metadata("design:paramtypes", [Function])
138
152
  ], ReceiveInventoryOrderItemResolver);
139
153
 
140
154
  export { ReceiveInventoryOrderItemResolver, ReceiveInventoryOrderResolver };
@@ -9,6 +9,15 @@ export declare class ShipInventoryOrderResolver {
9
9
  references({ id }: ShipInventoryOrderEntityClass, { loaders }: LoaderType<ShipInventoryOrderReferenceEntityClass>): Promise<ShipInventoryOrderReferenceDto[]>;
10
10
  }
11
11
  export declare class ShipInventoryOrderItemResolver {
12
+ private readonly materialEntity;
13
+ /**
14
+ * Bound to the `MaterialEntity` Symbol, so the DataLoader queries whichever
15
+ * material entity the consumer configured — the built-in `MaterialEntityClass`
16
+ * unless `materialEntity` was passed to `forRoot`/`forRootAsync`. Loading the
17
+ * built-in class outright would filter on its single-table-inheritance
18
+ * discriminator and resolve nothing for a custom entity.
19
+ */
20
+ constructor(materialEntity: new () => MaterialEntityClass);
12
21
  material({ materialId }: ShipInventoryOrderItemEntityClass, { loaders }: LoaderType<MaterialEntityClass>): Promise<MaterialDto>;
13
22
  batch({ batchId }: ShipInventoryOrderItemEntityClass, { loaders }: LoaderType<BatchEntityClass>): Promise<BatchDto | null>;
14
23
  saleId({ batchId }: ShipInventoryOrderItemEntityClass, { loaders }: LoaderType<BatchEntityClass>): Promise<string | null>;
@@ -18,6 +27,15 @@ export declare class ShipInventoryOrderItemResolver {
18
27
  shippedQuantity({ id }: ShipInventoryOrderItemEntityClass, { loaders }: LoaderType<ShipInventoryOrderChangeEntityClass>): Promise<number>;
19
28
  }
20
29
  export declare class ShipInventoryOrderReferenceResolver {
30
+ private readonly materialEntity;
31
+ /**
32
+ * Bound to the `MaterialEntity` Symbol, so the DataLoader queries whichever
33
+ * material entity the consumer configured — the built-in `MaterialEntityClass`
34
+ * unless `materialEntity` was passed to `forRoot`/`forRootAsync`. Loading the
35
+ * built-in class outright would filter on its single-table-inheritance
36
+ * discriminator and resolve nothing for a custom entity.
37
+ */
38
+ constructor(materialEntity: new () => MaterialEntityClass);
21
39
  shippedQuantity({ orderId, serialId }: ShipInventoryOrderReferenceEntityClass, { loaders }: LoaderType<ShipInventoryOrderItemEntityClass>): Promise<number>;
22
40
  material({ materialId }: ShipInventoryOrderReferenceEntityClass, { loaders }: LoaderType<MaterialEntityClass>): Promise<MaterialDto>;
23
41
  batch({ batchId }: ShipInventoryOrderReferenceEntityClass, { loaders }: LoaderType<BatchEntityClass>): Promise<BatchDto | null>;
@@ -1,4 +1,5 @@
1
1
  import { __decorate, __param, __metadata } from 'tslib';
2
+ import { Inject } from '@nestjs/common';
2
3
  import { ResolveField, Parent, Context, Resolver, ID, Int } from '@nestjs/graphql';
3
4
  import '../dto/loader-type.dto.js';
4
5
  import { LoaderDto } from '../dto/loader.dto.js';
@@ -24,7 +25,7 @@ import '../dto/scrape-order.dto.js';
24
25
  import '../dto/scrape-order-item.dto.js';
25
26
  import '../dto/quality-inspection-order.dto.js';
26
27
  import '../dto/quality-inspection-order-item.dto.js';
27
- import { ShipInventoryOrderEntityClass, ShipInventoryOrderItemEntityClass, ShipInventoryOrderReferenceEntityClass, MaterialEntityClass, BatchEntityClass, BatchGroupEntityClass, LocationEntityClass, LoadersEntityClass, ShipInventoryOrderChangeEntityClass } from '@rytass/wms-module-core';
28
+ import { ShipInventoryOrderEntityClass, ShipInventoryOrderItemEntityClass, MaterialEntity, ShipInventoryOrderReferenceEntityClass, BatchEntityClass, BatchGroupEntityClass, LocationEntityClass, LoadersEntityClass, ShipInventoryOrderChangeEntityClass } from '@rytass/wms-module-core';
28
29
  import { BatchDto } from '../dto/batch.dto.js';
29
30
  import { LocationDto } from '../dto/location.dto.js';
30
31
  import { ShipInventoryOrderReferenceDto } from '../dto/ship-inventory-order-reference.dto.js';
@@ -64,9 +65,20 @@ ShipInventoryOrderResolver = __decorate([
64
65
  Resolver(() => ShipInventoryOrderDto)
65
66
  ], ShipInventoryOrderResolver);
66
67
  let ShipInventoryOrderItemResolver = class ShipInventoryOrderItemResolver {
68
+ materialEntity;
69
+ /**
70
+ * Bound to the `MaterialEntity` Symbol, so the DataLoader queries whichever
71
+ * material entity the consumer configured — the built-in `MaterialEntityClass`
72
+ * unless `materialEntity` was passed to `forRoot`/`forRootAsync`. Loading the
73
+ * built-in class outright would filter on its single-table-inheritance
74
+ * discriminator and resolve nothing for a custom entity.
75
+ */
76
+ constructor(materialEntity) {
77
+ this.materialEntity = materialEntity;
78
+ }
67
79
  material({ materialId }, { loaders }) {
68
80
  return loaders.batchSingle.load({
69
- __entity: MaterialEntityClass,
81
+ __entity: this.materialEntity,
70
82
  __from: { id: materialId },
71
83
  });
72
84
  }
@@ -194,9 +206,22 @@ __decorate([
194
206
  __metadata("design:returntype", Promise)
195
207
  ], ShipInventoryOrderItemResolver.prototype, "shippedQuantity", null);
196
208
  ShipInventoryOrderItemResolver = __decorate([
197
- Resolver(() => ShipInventoryOrderItemDto)
209
+ Resolver(() => ShipInventoryOrderItemDto),
210
+ __param(0, Inject(MaterialEntity)),
211
+ __metadata("design:paramtypes", [Function])
198
212
  ], ShipInventoryOrderItemResolver);
199
213
  let ShipInventoryOrderReferenceResolver = class ShipInventoryOrderReferenceResolver {
214
+ materialEntity;
215
+ /**
216
+ * Bound to the `MaterialEntity` Symbol, so the DataLoader queries whichever
217
+ * material entity the consumer configured — the built-in `MaterialEntityClass`
218
+ * unless `materialEntity` was passed to `forRoot`/`forRootAsync`. Loading the
219
+ * built-in class outright would filter on its single-table-inheritance
220
+ * discriminator and resolve nothing for a custom entity.
221
+ */
222
+ constructor(materialEntity) {
223
+ this.materialEntity = materialEntity;
224
+ }
200
225
  async shippedQuantity({ orderId, serialId }, { loaders }) {
201
226
  const items = await loaders.batchMany.load({
202
227
  __entity: ShipInventoryOrderItemEntityClass,
@@ -211,7 +236,7 @@ let ShipInventoryOrderReferenceResolver = class ShipInventoryOrderReferenceResol
211
236
  }
212
237
  material({ materialId }, { loaders }) {
213
238
  return loaders.batchSingle.load({
214
- __entity: MaterialEntityClass,
239
+ __entity: this.materialEntity,
215
240
  __from: { id: materialId },
216
241
  });
217
242
  }
@@ -267,7 +292,9 @@ __decorate([
267
292
  __metadata("design:returntype", Promise)
268
293
  ], ShipInventoryOrderReferenceResolver.prototype, "items", null);
269
294
  ShipInventoryOrderReferenceResolver = __decorate([
270
- Resolver(() => ShipInventoryOrderReferenceDto)
295
+ Resolver(() => ShipInventoryOrderReferenceDto),
296
+ __param(0, Inject(MaterialEntity)),
297
+ __metadata("design:paramtypes", [Function])
271
298
  ], ShipInventoryOrderReferenceResolver);
272
299
 
273
300
  export { ShipInventoryOrderItemResolver, ShipInventoryOrderReferenceResolver, ShipInventoryOrderResolver };
@@ -4,6 +4,15 @@ import { type LoaderType } from '../helpers';
4
4
  import { LocationDto } from '../dto/location.dto';
5
5
  import { BatchDto } from '../dto/batch.dto';
6
6
  export declare class StockResolver {
7
+ private readonly materialEntity;
8
+ /**
9
+ * Bound to the `MaterialEntity` Symbol, so the DataLoader queries whichever
10
+ * material entity the consumer configured — the built-in `MaterialEntityClass`
11
+ * unless `materialEntity` was passed to `forRoot`/`forRootAsync`. Loading the
12
+ * built-in class outright would filter on its single-table-inheritance
13
+ * discriminator and resolve nothing for a custom entity.
14
+ */
15
+ constructor(materialEntity: new () => MaterialEntityClass);
7
16
  material({ materialId }: StockEntityClass, { loaders }: LoaderType<MaterialEntityClass>): Promise<MaterialDto>;
8
17
  location({ locationId }: StockEntityClass, { loaders }: LoaderType<LocationEntityClass>): Promise<LocationDto>;
9
18
  batch({ batchId }: StockEntityClass, { loaders }: LoaderType<BatchEntityClass>): Promise<BatchDto | null>;
@@ -1,4 +1,5 @@
1
1
  import { __decorate, __param, __metadata } from 'tslib';
2
+ import { Inject } from '@nestjs/common';
2
3
  import { ResolveField, Parent, Context, Resolver } from '@nestjs/graphql';
3
4
  import '../dto/loader-type.dto.js';
4
5
  import '../dto/loader.dto.js';
@@ -24,14 +25,25 @@ import '../dto/scrape-order.dto.js';
24
25
  import '../dto/scrape-order-item.dto.js';
25
26
  import '../dto/quality-inspection-order.dto.js';
26
27
  import '../dto/quality-inspection-order-item.dto.js';
27
- import { StockEntityClass, MaterialEntityClass, LocationEntityClass, BatchEntityClass, BatchGroupEntityClass } from '@rytass/wms-module-core';
28
+ import { StockEntityClass, MaterialEntity, LocationEntityClass, BatchEntityClass, BatchGroupEntityClass } from '@rytass/wms-module-core';
28
29
  import { LocationDto } from '../dto/location.dto.js';
29
30
  import { BatchDto } from '../dto/batch.dto.js';
30
31
 
31
32
  let StockResolver = class StockResolver {
33
+ materialEntity;
34
+ /**
35
+ * Bound to the `MaterialEntity` Symbol, so the DataLoader queries whichever
36
+ * material entity the consumer configured — the built-in `MaterialEntityClass`
37
+ * unless `materialEntity` was passed to `forRoot`/`forRootAsync`. Loading the
38
+ * built-in class outright would filter on its single-table-inheritance
39
+ * discriminator and resolve nothing for a custom entity.
40
+ */
41
+ constructor(materialEntity) {
42
+ this.materialEntity = materialEntity;
43
+ }
32
44
  material({ materialId }, { loaders }) {
33
45
  return loaders.batchSingle.load({
34
- __entity: MaterialEntityClass,
46
+ __entity: this.materialEntity,
35
47
  __from: { id: materialId },
36
48
  });
37
49
  }
@@ -77,7 +89,9 @@ __decorate([
77
89
  __metadata("design:returntype", Promise)
78
90
  ], StockResolver.prototype, "batch", null);
79
91
  StockResolver = __decorate([
80
- Resolver(() => StockDto)
92
+ Resolver(() => StockDto),
93
+ __param(0, Inject(MaterialEntity)),
94
+ __metadata("design:paramtypes", [Function])
81
95
  ], StockResolver);
82
96
 
83
97
  export { StockResolver };
@@ -11,6 +11,15 @@ export declare class TransferOrderResolver {
11
11
  targetLoader({ id }: TransferOrderDto, { loaders }: LoaderType<TransferOrderItemEntityClass>, loaderLoader: LoaderType<LoadersEntityClass>): Promise<LoaderDto | null>;
12
12
  }
13
13
  export declare class TransferOrderItemResolver {
14
+ private readonly materialEntity;
15
+ /**
16
+ * Bound to the `MaterialEntity` Symbol, so the DataLoader queries whichever
17
+ * material entity the consumer configured — the built-in `MaterialEntityClass`
18
+ * unless `materialEntity` was passed to `forRoot`/`forRootAsync`. Loading the
19
+ * built-in class outright would filter on its single-table-inheritance
20
+ * discriminator and resolve nothing for a custom entity.
21
+ */
22
+ constructor(materialEntity: new () => MaterialEntityClass);
14
23
  material({ materialId }: TransferOrderItemEntityClass, { loaders }: LoaderType<MaterialEntityClass>): Promise<MaterialDto>;
15
24
  batch({ batchId }: TransferOrderItemEntityClass, { loaders }: LoaderType<BatchEntityClass>): Promise<BatchDto | null>;
16
25
  location({ locationId }: TransferOrderItemEntityClass, { loaders }: LoaderType<LocationEntityClass>): Promise<LocationDto | null>;
@@ -1,8 +1,9 @@
1
1
  import { __decorate, __param, __metadata } from 'tslib';
2
+ import { Inject } from '@nestjs/common';
2
3
  import { ResolveField, Parent, Context, Resolver, ID, Int } from '@nestjs/graphql';
3
4
  import { TransferOrderDto } from '../dto/transfer-order.dto.js';
4
5
  import { TransferOrderItemDto } from '../dto/transfer-order-item.dto.js';
5
- import { TransferOrderItemEntityClass, LocationEntityClass, LoadersEntityClass, MaterialEntityClass, BatchEntityClass, BatchGroupEntityClass, TransferOrderChangeEntityClass } from '@rytass/wms-module-core';
6
+ import { TransferOrderItemEntityClass, MaterialEntity, LocationEntityClass, LoadersEntityClass, BatchEntityClass, BatchGroupEntityClass, TransferOrderChangeEntityClass } from '@rytass/wms-module-core';
6
7
  import '../dto/loader-type.dto.js';
7
8
  import { LoaderDto } from '../dto/loader.dto.js';
8
9
  import { MaterialDto } from '../dto/material.dto.js';
@@ -90,9 +91,20 @@ TransferOrderResolver = __decorate([
90
91
  Resolver(() => TransferOrderDto)
91
92
  ], TransferOrderResolver);
92
93
  let TransferOrderItemResolver = class TransferOrderItemResolver {
94
+ materialEntity;
95
+ /**
96
+ * Bound to the `MaterialEntity` Symbol, so the DataLoader queries whichever
97
+ * material entity the consumer configured — the built-in `MaterialEntityClass`
98
+ * unless `materialEntity` was passed to `forRoot`/`forRootAsync`. Loading the
99
+ * built-in class outright would filter on its single-table-inheritance
100
+ * discriminator and resolve nothing for a custom entity.
101
+ */
102
+ constructor(materialEntity) {
103
+ this.materialEntity = materialEntity;
104
+ }
93
105
  material({ materialId }, { loaders }) {
94
106
  return loaders.batchSingle.load({
95
- __entity: MaterialEntityClass,
107
+ __entity: this.materialEntity,
96
108
  __from: { id: materialId },
97
109
  });
98
110
  }
@@ -212,7 +224,9 @@ __decorate([
212
224
  __metadata("design:returntype", Promise)
213
225
  ], TransferOrderItemResolver.prototype, "transferredQuantity", null);
214
226
  TransferOrderItemResolver = __decorate([
215
- Resolver(() => TransferOrderItemDto)
227
+ Resolver(() => TransferOrderItemDto),
228
+ __param(0, Inject(MaterialEntity)),
229
+ __metadata("design:paramtypes", [Function])
216
230
  ], TransferOrderItemResolver);
217
231
 
218
232
  export { TransferOrderItemResolver, TransferOrderResolver };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rytass/wms-module-graphql",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Rytass WMS Module — GraphQL schema, resolvers and DTOs for warehouse management",
5
5
  "keywords": [
6
6
  "rytass",
@@ -57,7 +57,7 @@
57
57
  "@nestjs/apollo": ">=13.0.0 <13.2.0",
58
58
  "@nestjs/common": "^11.0.0",
59
59
  "@nestjs/graphql": "^13.0.0",
60
- "@rytass/wms-module-core": "^0.3.0",
60
+ "@rytass/wms-module-core": "^0.4.0",
61
61
  "graphql": "^16.0.0",
62
62
  "reflect-metadata": "^0.1.13 || ^0.2.0",
63
63
  "rxjs": "^7.8.0",