Merged PR 1993: feat(action-handler, printing, schemas)

1 commit: Bestellbestätigung drucken
2. commit: Schemas
3. commit: Action/Command handler

feat(action-handler, printing, schemas): add handle command service for automated action execution

Implement HandleCommandService and facade to execute order actions automatically
after reward collection. Add action handler infrastructure with 23 handlers
(Accepted, Arrived, Assembled, etc.). Integrate automatic receipt fetching for
print commands. Add schema validation for command handling and receipt queries.
Update reward confirmation to trigger actions after successful collection.

- Add HandleCommandService with command orchestration
- Add HandleCommandFacade as public API layer
- Create schemas: HandleCommandSchema, FetchReceiptsByOrderItemSubsetIdsSchema
- Add helpers: getMainActions, buildItemQuantityMap
- Register 23 action handlers in reward confirmation routes
- Support PRINT_SHIPPINGNOTE and PRINT_SMALLAMOUNTINVOICE auto-fetching
- Update CoreCommandModule for forRoot/forChild patterns
- Add comprehensive unit tests for new services and helpers
- Apply prettier formatting to command and printing modules

Ref: #5394
This commit is contained in:
Nino Righi
2025-11-03 20:00:53 +00:00
committed by Lorenz Hilpert
parent 53a062dcde
commit a49ea25fd0
53 changed files with 2453 additions and 447 deletions

View File

@@ -3,31 +3,49 @@ import { ActionHandler } from './action-handler.interface';
import { CommandService } from './command.service';
import { FEATURE_ACTION_HANDLERS, ROOT_ACTION_HANDLERS } from './tokens';
export function provideActionHandlers(actionHandlers: Type<ActionHandler>[]): Provider[] {
export function provideActionHandlers(
actionHandlers: Type<ActionHandler>[],
): Provider[] {
return [
CommandService,
actionHandlers.map((handler) => ({ provide: FEATURE_ACTION_HANDLERS, useClass: handler, multi: true })),
actionHandlers.map((handler) => ({
provide: FEATURE_ACTION_HANDLERS,
useClass: handler,
multi: true,
})),
];
}
@NgModule({})
export class CoreCommandModule {
static forRoot(actionHandlers: Type<ActionHandler>[]): ModuleWithProviders<CoreCommandModule> {
static forRoot(
actionHandlers: Type<ActionHandler>[],
): ModuleWithProviders<CoreCommandModule> {
return {
ngModule: CoreCommandModule,
providers: [
CommandService,
actionHandlers.map((handler) => ({ provide: ROOT_ACTION_HANDLERS, useClass: handler, multi: true })),
actionHandlers.map((handler) => ({
provide: ROOT_ACTION_HANDLERS,
useClass: handler,
multi: true,
})),
],
};
}
static forChild(actionHandlers: Type<ActionHandler>[]): ModuleWithProviders<CoreCommandModule> {
static forChild(
actionHandlers: Type<ActionHandler>[],
): ModuleWithProviders<CoreCommandModule> {
return {
ngModule: CoreCommandModule,
providers: [
CommandService,
actionHandlers.map((handler) => ({ provide: FEATURE_ACTION_HANDLERS, useClass: handler, multi: true })),
actionHandlers.map((handler) => ({
provide: FEATURE_ACTION_HANDLERS,
useClass: handler,
multi: true,
})),
],
};
}

View File

@@ -15,10 +15,13 @@ export class CommandService {
for (const action of actions) {
const handler = this.getActionHandler(action);
if (!handler) {
console.error('CommandService.handleCommand', 'Action Handler does not exist', { action });
console.error(
'CommandService.handleCommand',
'Action Handler does not exist',
{ action },
);
throw new Error('Action Handler does not exist');
}
data = await handler.handler(data, this);
}
return data;
@@ -29,10 +32,18 @@ export class CommandService {
}
getActionHandler(action: string): ActionHandler | undefined {
const featureActionHandlers: ActionHandler[] = this.injector.get(FEATURE_ACTION_HANDLERS, []);
const rootActionHandlers: ActionHandler[] = this.injector.get(ROOT_ACTION_HANDLERS, []);
const featureActionHandlers: ActionHandler[] = this.injector.get(
FEATURE_ACTION_HANDLERS,
[],
);
const rootActionHandlers: ActionHandler[] = this.injector.get(
ROOT_ACTION_HANDLERS,
[],
);
let handler = [...featureActionHandlers, ...rootActionHandlers].find((handler) => handler.action === action);
let handler = [...featureActionHandlers, ...rootActionHandlers].find(
(handler) => handler.action === action,
);
if (this._parent && !handler) {
handler = this._parent.getActionHandler(action);

View File

@@ -20,9 +20,15 @@ export class PrintShippingNoteActionHandler extends ActionHandler<OrderItemsCont
async printShippingNoteHelper(printer: string, receipts: ReceiptDTO[]) {
try {
for (const group of groupBy(receipts, (receipt) => receipt?.buyer?.buyerNumber)) {
for (const group of groupBy(
receipts,
(receipt) => receipt?.buyer?.buyerNumber,
)) {
await this.domainPrinterService
.printShippingNote({ printer, receipts: group?.items?.map((r) => r?.id) })
.printShippingNote({
printer,
receipts: group?.items?.map((r) => r?.id),
})
.toPromise();
}
return {
@@ -38,7 +44,9 @@ export class PrintShippingNoteActionHandler extends ActionHandler<OrderItemsCont
}
async handler(data: OrderItemsContext): Promise<OrderItemsContext> {
const printerList = await this.domainPrinterService.getAvailableLabelPrinters().toPromise();
const printerList = await this.domainPrinterService
.getAvailableLabelPrinters()
.toPromise();
const receipts = data?.receipts?.filter((r) => r?.receiptType & 1);
let printer: Printer;
@@ -53,7 +61,8 @@ export class PrintShippingNoteActionHandler extends ActionHandler<OrderItemsCont
data: {
printImmediately: !this._environmentSerivce.matchTablet(),
printerType: 'Label',
print: async (printer) => await this.printShippingNoteHelper(printer, receipts),
print: async (printer) =>
await this.printShippingNoteHelper(printer, receipts),
} as PrintModalData,
})
.afterClosed$.toPromise();