Page extension - Customization on View stock by product site
Less than to read
In the example displayed in this page, the MobileViewStockByProductSiteProductDetails page is altered to match the following requirements:
- Remove the Owner fields and value on mobile-view-stock-by-product-site-product-details.ts.
- Add the new details list SPE01List and display the item SPE01 on mobile-view-stock-by-product-site-product-details.ts.
- Add an action button on mobile-view-stock-by-product-site-product-details.ts to jump to the Stock change transaction.
- Open the Stock change details page (MobileInternalStockChangeDetails) while keeping the same stock line data.
Start with creating a new SPE package:
Run the following command in the root of your project:
cd x3-services && pnpm run generate && cd && pnpm run build:x3-services
The result of this command is stored in the folder x3-spe.
In the page-extensions folder located inside the lib folder, a file is created matching the page to be customized. The file name is mobile-view-stock-by-product-site-product-details-extension.
import * as ui from '@sage/xtrem-ui';
import { GraphApi } from "@sage/x3-stock-api";
import { MobileViewStockByProductSiteProductDetails } from "@sage/x3-stock/build/lib/pages/mobile-view-stock-by-product-site-product-details";
@ui.decorators.pageExtension<MobileViewStockByProductSiteProductDetailsExtension>({
extends: '@sage/x3-stock/MobileViewStockByProductSiteProductDetails',
})
export class MobileViewStockByProductSiteProductDetailsExtension extends ui.PageExtension<MobileViewStockByProductSiteProductDetails,GraphApi> {
}
declare module '@sage/x3-stock/build/lib/pages/mobile-view-stock-by-product-site-product-details' {
interface MobileViewStockByProductSiteProductDetails extends MobileViewStockByProductSiteProductDetailsExtension {}
}
Customization step-by-step
For example, begin with this page:
Then try the following customizations.
Remove the Owner fields and value on mobile-view-stock-by-product-site-product-details.ts
In the mobile-view-stock-by-product-site-product-details.ts, the detailListField component is named stockDetailLines. To customize it, override it as follows:
@ui.decorators.detailListFieldOverride<MobileViewStockByProductSiteProductDetailsExtension>({
fieldOverrides: [
ui.nestedFieldOverrides.text({
bind: 'owner',
isHidden: true,
}),
],
})
stockDetailLines: ui.fields.DetailList;
The resulting page based on this action is:
Add the new details list SPE01List and display the item SPE01 on mobile-view-stock-by-product-site-product-details.ts
@ui.decorators.pageExtension<MobileViewStockByProductSiteProductDetailsExtension>({
extends: '@sage/x3-stock/MobileViewStockByProductSiteProductDetails',
async onLoad() {
await this._init();
this.spe01Line.value = [
{
_spacerColumn: '',
spe01: 'SPE01',
},
] as Partial<any>[];
},
})
@ui.decorators.detailListField<MobileViewStockByProductSiteProductDetailsExtension>({
parent() {
return this.mainBlock;
},
fields: [
ui.nestedFields.text({
bind: '_spacerColumn',
isReadOnly: true,
isTransient: true,
}),
ui.nestedFields.text({
bind: 'spe01',
title: 'Specific field',
isReadOnly: true,
isTransient: true,
}),
],
})
spe01Line: ui.fields.DetailList;
The resulting page based on this action is:
Add an action button on mobile-view-stock-by-product-site-product-details.ts to jump to the Stock change transaction
@ui.decorators.buttonField<MobileViewStockByProductSiteProductDetailsExtension>({
parent() {
return this.secondBlock;
},
map() {
return 'Go to Stock Change';
},
isFullWidth: true,
async onClick() {
this.$.router.goTo('@sage/x3-stock/MobileInternalStockChangeDetails', {
_id: `${this._productSite?._id}`,
mobileSettings: JSON.stringify({ ...this._mobileSettings }),
stockSite: JSON.stringify(this._productSite?.stockSite),
selectedProduct: JSON.stringify(this._productSite),
globalTradeItemNumber: this._productSite.product?.globalTradeItemNumber ?? '',
});
},
})
goToStockChangeButton: ui.fields.Button;
The resulting page based on this action is:
Open the Stock change details page MobileInternalStockChangeDetails while keeping the same stock line data
To go directly from the customized MobileViewStockByProductSiteProductDetailsExtension page to the MobileInternalStockChangeDetails page, the necessary context must be initialized. This means passing the parameters stock line ID
, stock site
, product
, mobileSettings
, and the global trade item number.
To make a stock change, an input transaction must be defined. To define an input transaction, the transaction field must be added:
import { ProductSite } from '@sage/x3-master-data-api';
import { ProductInput } from '@sage/x3-master-data-api-partial';
import { StockChangeLineInput, StockEntryTransaction } from '@sage/x3-stock-api';
import { GraphApi, MobileSettings, Stock } from '@sage/x3-stock-data-api';
import { MobileViewStockByProductSiteProductDetails } from '@sage/x3-stock/build/lib/pages/mobile-view-stock-by-product-site-product-details';
import { inputsStockChange } from '@sage/x3-stock/lib/pages/mobile-internal-stock-change';
import { ExtractEdgesPartial, extractEdges } from '@sage/xtrem-client';
import * as ui from '@sage/xtrem-ui';
type DeepPartial<T> = T extends Object ? { [K in keyof T]?: DeepPartial<T[K]> } : T;
type PartialStockEntryTransaction = DeepPartial<StockEntryTransaction>;
@ui.decorators.pageExtension<MobileViewStockByProductSiteProductDetailsExtension>({
extends: '@sage/x3-stock/MobileViewStockByProductSiteProductDetails',
async onLoad() {
await this._init();
this.spe01.value = 'SPE01';
},
})
export class MobileViewStockByProductSiteProductDetailsExtension extends ui.PageExtension<
MobileViewStockByProductSiteProductDetails,
GraphApi
> {
_mobileSettings: MobileSettings;
_productSite: ExtractEdgesPartial<ProductSite>;
_savedObject: inputsStockChange;
_transactions: PartialStockEntryTransaction[];
@ui.decorators.detailListFieldOverride<MobileViewStockByProductSiteProductDetailsExtension>({
fieldOverrides: [
ui.nestedFieldOverrides.text({
bind: 'owner',
isHidden: true,
}),
],
})
stockDetailLines: ui.fields.DetailList;
@ui.decorators.block<MobileViewStockByProductSiteProductDetailsExtension>({
parent() {
return this.mainSection;
},
})
secondBlock: ui.containers.Block;
@ui.decorators.textField<MobileViewStockByProductSiteProductDetailsExtension>({
parent() {
return this.mainBlock;
},
isTransient: true,
isReadOnly: true,
})
spe01: ui.fields.Text;
@ui.decorators.dropdownListField<MobileViewStockByProductSiteProductDetailsExtension>({
parent() {
return this.mainBlock;
},
title: 'Transaction',
isTransient: true,
})
transaction: ui.fields.DropdownList;
@ui.decorators.buttonField<MobileViewStockByProductSiteProductDetailsExtension>({
parent() {
return this.secondBlock;
},
map() {
return 'Go to Stock Change';
},
isFullWidth: true,
async onClick() {
this.$.router.goTo('@sage/x3-stock/MobileInternalStockChangeDetails', {
_id: `${this._productSite?._id}`,
mobileSettings: JSON.stringify({ ...this._mobileSettings }),
stockSite: JSON.stringify(this._productSite?.stockSite),
selectedProduct: JSON.stringify(this._productSite),
globalTradeItemNumber: this._productSite.product?.globalTradeItemNumber ?? '',
});
},
})
goToStockChangeButton: ui.fields.Button;
private async _init(): Promise<void> {
this._mobileSettings = JSON.parse(this.$.storage.get('mobile-settings-stock-change') as string);
await this._readTransaction();
await this._readProductSite();
this._savedObject = {
stockChange: {
id: '',
stockChangeLines: new Array<StockChangeLineInput>(),
},
currentLine: 0,
username: this.$.username ?? null,
started: false,
selectedTransaction: this._transactions[0] ?? undefined,
selectedProduct: this._productSite.product as ProductInput ?? undefined,
};
this.$.storage.set('mobile-stockChange', JSON.stringify({ ...this._savedObject }));
}
private async _readProductSite(): Promise<void> {
const _result = await this.$.graph
.node('@sage/x3-master-data/ProductSite')
.read(
{
_id: true,
isLocationManaged: true,
isLicensePlateNumberManaged: true,
distinctCountOfLocations: true,
countOfStockRecords: true,
distinctCountOfStockQuantity: true,
distinctCountOfLots: true,
distinctCountOfSublots: true,
stockUnitCode: true,
product: {
code: true,
localizedDescription1: true,
upc: true,
lotManagementMode: true,
globalTradeItemNumber: true,
},
stockSite: {
code: true,
},
},
`${(this as unknown as MobileViewStockByProductSiteProductDetails).product.value}|${(this as unknown as MobileViewStockByProductSiteProductDetails).site.value}`,
)
.execute();
this._productSite = _result;
}
private async _readTransaction(): Promise<void | never> {
try {
this._transactions = extractEdges(
await this.$.graph
.node('@sage/x3-stock/StockEntryTransaction')
.query(
ui.queryUtils.edgesSelector(
{
code: true,
isActive: true,
stockAutomaticJournal: {
code: true,
},
localizedDescription: true,
transactionType: true,
identifier1Destination: true,
identifier2Destination: true,
identifier1Detail: true,
identifier2Detail: true,
printingMode: true,
defaultStockMovementGroup: {
code: true,
},
stockMovementCode: {
code: true,
},
companyOrSiteGroup: {
group: true,
},
},
{
filter: {
transactionType: 'stockChange',
isActive: true,
stockChangeDestination: 'internal',
stockChangeAccessMode: { _ne: 'containerNumber' },
},
},
),
)
.execute(),
);
} catch (err) {
ui.console.error(err);
}
if (this._transactions) this.transaction.options = this._transactions.map(trs => trs.code);
this.transaction.value = this._transactions[0].code ?? '';
}
}
declare module '@sage/x3-stock/build/lib/pages/mobile-view-stock-by-product-site-product-details' {
interface MobileViewStockByProductSiteProductDetails extends MobileViewStockByProductSiteProductDetailsExtension {}
}
This is the result of these changes applied to the mobile automation pages: