Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
953 changes: 831 additions & 122 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion src/app/ew/fabric/FabricBaseModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
updatedBy,
} from '@decaf-ts/core';
import { description, uses } from '@decaf-ts/decoration';
import { hideOn, uielement } from '@decaf-ts/ui-decorators';
import { hideOn, uielement, uiorder } from '@decaf-ts/ui-decorators';
import { OperationKeys, version } from '@decaf-ts/db-decorators';

//@uses(FabricFlavour)
Expand All @@ -23,6 +23,7 @@ export class FabricBaseModel extends Model {
readonly: true,
})
@hideOn(OperationKeys.CREATE, OperationKeys.UPDATE)
@uiorder('last')
createdAt!: Date;

@column()
Expand All @@ -34,6 +35,7 @@ export class FabricBaseModel extends Model {
readonly: true,
})
@hideOn(OperationKeys.CREATE, OperationKeys.UPDATE)
@uiorder('last')
updatedAt!: Date;

@column()
Expand All @@ -44,6 +46,7 @@ export class FabricBaseModel extends Model {
readonly: true,
})
@hideOn(OperationKeys.CREATE, OperationKeys.UPDATE)
@uiorder('last')
version!: number;

constructor(arg?: ModelArg<FabricBaseModel>) {
Expand Down
4 changes: 3 additions & 1 deletion src/app/ew/fabric/FabricIdentifiedModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { type ModelArg } from '@decaf-ts/decorator-validation';
import { column, createdBy, index, OrderDirection, updatedBy } from '@decaf-ts/core';
import { description, uses } from '@decaf-ts/decoration';
import { FabricBaseModel } from './FabricBaseModel';
import { hideOn, uielement } from '@decaf-ts/ui-decorators';
import { hideOn, uielement, uiorder } from '@decaf-ts/ui-decorators';
import { OperationKeys } from '@decaf-ts/db-decorators';

//@uses(FabricFlavour)
Expand All @@ -16,6 +16,7 @@ export class FabricIdentifiedModel extends FabricBaseModel {
readonly: true,
})
@hideOn(OperationKeys.CREATE, OperationKeys.UPDATE)
@uiorder('last')
createdBy!: string;

@column()
Expand All @@ -27,6 +28,7 @@ export class FabricIdentifiedModel extends FabricBaseModel {
readonly: true,
})
@hideOn(OperationKeys.CREATE, OperationKeys.UPDATE)
@uiorder('last')
updatedBy!: string;
constructor(arg?: ModelArg<FabricIdentifiedModel>) {
super(arg);
Expand Down
7 changes: 5 additions & 2 deletions src/app/ew/fabric/Product.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Model, ModelArg } from '@decaf-ts/decorator-validation';
import { model, required, type } from '@decaf-ts/decorator-validation';
import { maxlength, min, minlength, model, required, type } from '@decaf-ts/decorator-validation';
// import { gtin, TableNames } from "@pharmaledgerassoc/ptp-toolkit/shared";
import { propMetadata, Constructor } from '@decaf-ts/decoration';

Expand Down Expand Up @@ -38,6 +38,7 @@ import { audit } from './utils';
import { FileUploadComponent } from 'src/lib/components';
import { ProductImageHandler } from './handlers/ProductImageHandler';
import { ProductHandler } from './handlers/ProductHandler';
import { gtin } from './gtin';

// @BlockOperations([OperationKeys.DELETE])
//@uses(FabricFlavour)
Expand All @@ -46,14 +47,15 @@ import { ProductHandler } from './handlers/ProductHandler';
@uilayout('ngx-decaf-crud-form', true, 1, { empty: { showButton: false } })
@model()
export class Product extends Cacheable {
//@gtin()
// //@cache()
// @assignProductOwner()
@audit()
@pk({ type: String, generated: false })
@gtin(['errors.gtin.digits', 'errors.gtin.checksum', 'errors.gtin.fallback'])
@uilistprop('title')
@uielement('ngx-decaf-crud-field', {
label: 'product.productCode.label',
updateOn: 'change',
placeholder: 'product.productCode.placeholder',
// readonly: () => {
// return (this as unknown as CrudFieldComponent).operation !== OperationKeys.CREATE;
Expand All @@ -73,6 +75,7 @@ export class Product extends Cacheable {
})
@uilistprop('title')
@uilayoutprop(2)
@minlength(2)
inventedName!: string;

//@cache()
Expand Down
146 changes: 146 additions & 0 deletions src/app/ew/fabric/gtin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { apply } from '@decaf-ts/decoration';
import {
innerValidationDecorator,
validator,
ValidatorOptions,
} from '@decaf-ts/decorator-validation';
import { UIValidator } from '@decaf-ts/ui-decorators';

const GTIN_VALIDATION_KEY = 'gtin';
export const GTIN_MISSING_DIGITS_ERROR_MESSAGE =
'Gtin length is 14. you are missing {0} more digits';
export const GTIN_NEXT_DIGIT_ERROR_MESSAGE = 'to be a valid gtin your next digit must be {0}';
export const GTIN_VALIDATION_ERROR_MESSAGE = 'Not a valid Gtin';

export function generateGtin(): string {
function pad(num: number, width: number, padding: string = '0') {
const n = num + '';
return n.length >= width ? n : new Array(width - n.length + 1).join(padding) + n;
}

const beforeChecksum = pad(Math.floor(Math.random() * 9999999999999), 13); // has to be 13. the checksum is the 4th digit
const checksum = calculateGtinCheckSum(beforeChecksum);
return `${beforeChecksum}${checksum}`;
}

// https://www.gs1.org/services/how-calculate-check-digit-manually
function calculateGtinCheckSum(digits: string): string {
digits = '' + digits;
if (digits.length !== 13) throw new Error('needs to received 13 digits');
const multiplier = [3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3];
let sum = 0;
try {
// multiply each digit for its multiplier according to the table
for (let i = 0; i < 13; i++) sum += parseInt(digits.charAt(i)) * multiplier[i];

// Find the nearest equal or higher multiple of ten
const remainder = sum % 10;
let nearest;
if (remainder === 0) nearest = sum;
else nearest = sum - remainder + 10;

return nearest - sum + '';
} catch (e) {
throw new Error(`Did this received numbers? ${e}`);
}
}

type MessageSource = string | string[] | undefined;

@validator(GTIN_VALIDATION_KEY)
export class GtinValidator extends UIValidator {
constructor(message: string = GTIN_VALIDATION_ERROR_MESSAGE) {
super(message, 'string', 'number');
}

private resolveMessages(message?: MessageSource): [string, string, string] {
if (Array.isArray(message)) {
const [missingDigits, nextDigit, fallback] = message;
return [
missingDigits ?? GTIN_MISSING_DIGITS_ERROR_MESSAGE,
nextDigit ?? missingDigits ?? GTIN_NEXT_DIGIT_ERROR_MESSAGE,
fallback ?? nextDigit ?? missingDigits ?? this.message,
];
}
if (typeof message === 'string' && message.length > 0) {
return [message, message, message];
}
return [GTIN_MISSING_DIGITS_ERROR_MESSAGE, GTIN_NEXT_DIGIT_ERROR_MESSAGE, this.message];
}

hasErrors(value: number | string, options: ValidatorOptions): string | undefined {
if (value === undefined || value === null) return;
if (typeof value !== 'string' && typeof value !== 'number') {
return this.getMessage(this.message);
}

const [missingDigitsMessage, checksumMessage, fallbackMessage] = this.resolveMessages(
options?.message || this.message,
);

const gtin = `${value}`.trim();
if (!gtin) return this.getMessage(fallbackMessage);

if (!/^\d+$/.test(gtin)) {
return this.getMessage(fallbackMessage);
}

const length = gtin.length;

if (length < 13) {
return this.getMessage(missingDigitsMessage, 14 - length);
}

if (length === 13) {
const checksum = calculateGtinCheckSum(gtin);
return this.getMessage(checksumMessage, checksum);
}

if (length > 14) {
return this.getMessage(fallbackMessage);
}
const digits = gtin.slice(0, 13);
const checksum = calculateGtinCheckSum(digits);
return checksum === gtin.charAt(13) ? undefined : this.getMessage(fallbackMessage);
}
}

// hasErrors(value: number | string, options?: ValidatorOptions): string | undefined {
// if (value === undefined) return;

// const { message } = options || {};
// const [digitsError, checkSumError, fallbackError] = message as unknown as string[];
// const gtin = value + '';

// let checksum: string;

// const length = gtin.length;
// if (length > 14) return this.getMessage(fallbackError);
// if (length < 13) return this.getMessage(digitsError, length);
// if (length === 13) {
// checksum = calculateGtinCheckSum(gtin);
// return this.getMessage(checkSumError, checksum);
// }

// if (!gtin.match(/\d{14}/g)) return this.getMessage(fallbackError || this.message);
// const digits = gtin.slice(0, 13);
// checksum = calculateGtinCheckSum(digits);
// return parseInt(checksum) === parseInt(gtin.charAt(13))
// ? undefined
// : this.getMessage(message || this.message);
// }

export const gtin = (
messages: string[] = [
GTIN_MISSING_DIGITS_ERROR_MESSAGE,
GTIN_NEXT_DIGIT_ERROR_MESSAGE,
GTIN_VALIDATION_ERROR_MESSAGE,
],
) => {
return apply(
innerValidationDecorator(gtin, GTIN_VALIDATION_KEY, {
message: messages,
async: false,
}),
);
};
71 changes: 40 additions & 31 deletions src/app/utils/FakerRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { Audit } from '../ew/fabric/Audit';
import { AuditOperations, UserGroup } from '../ew/fabric/constants';
import { ProductStrength } from '../ew/fabric/ProductStrength';
import { getModelAndRepository } from 'src/lib/engine/helpers';
import { generate } from 'rxjs';
import { generateGtin } from '../ew/fabric/gtin';
enum ProductNames {
aspirin = 'Aspirin',
ibuprofen = 'Ibuprofen',
Expand Down Expand Up @@ -38,6 +40,30 @@ enum ProductNames {
// furosemide = "Furosemide"
}

async function getProducts(): Promise<Product[]> {
const repo = getModelAndRepository('Product');
if (repo) {
const { repository } = repo;
const query = await repository.select().execute();
if (query.length) {
return query as Product[];
}
}
return [];
}

async function getBatchs(): Promise<Batch[]> {
const repo = getModelAndRepository('Batch');
if (repo) {
const { repository } = repo;
const query = await repository.select().execute();
if (query.length) {
return query as Batch[];
}
}
return [];
}

export class FakerRepository<T extends Model> extends DecafFakerRepository<T> {
public override async initialize(): Promise<void> {
await super.initialize();
Expand Down Expand Up @@ -65,9 +91,11 @@ export class FakerRepository<T extends Model> extends DecafFakerRepository<T> {
}
case Product.name: {
this.limit = 2;
this.propFnMapper = {
productCode: () => generateGtin(),
};
data = (await this.generateData<T & Product>(ProductNames, 'inventedName', 'string')).map(
(item: Partial<Product>, index: number) => {
item.productCode = index === 1 ? '00000000000013' : `0${index + 1}`.padStart(14, '0');
delete item?.['imageData'];
item.markets = [];
item.strengths = [];
Expand All @@ -77,22 +105,16 @@ export class FakerRepository<T extends Model> extends DecafFakerRepository<T> {
break;
}
case Batch.name: {
const repo = getModelAndRepository('Product');
const products = await getProducts();
this.limit = 2;
data = await this.generateData<Batch>();
data = [
...(await Promise.all(
data.map(async (item: Partial<Batch>, index: number) => {
const productCode = '00000000000013';
// this.limit === 1
// ? '00000000000013'
// : `0${Math.floor(Math.random() * 5) + 1}`.padStart(14, '0');
const repo = getModelAndRepository('Product');
const productCode = this.pickRandomValue(products.map((p) => p.productCode));
const product = products.find((p) => p.productCode === productCode);
item.productCode = productCode;
if (repo) {
const { repository } = repo;
const product = (await repository.read(productCode)) as Product;
}

item.batchNumber =
`bt_${productCode}_${index % 2 === 0 ? 'aspirin' : this.pickRandomValue(Object.values(ProductNames))}`.trim();
// item.batchNumber = `bt_${productCode}_${item['nameMedicinalProduct']}`.trim();
Expand All @@ -109,29 +131,14 @@ export class FakerRepository<T extends Model> extends DecafFakerRepository<T> {
break;
}
case Leaflet.name: {
const products = await getProducts();
this.limit = 2;
this.propFnMapper = {
productCode: () => this.pickRandomValue(products.map((p) => p.productCode)),
lang: () => this.pickRandomValue(['en', 'pt-br']),
productCode: () => {
const productCode = `013`;
return productCode.padStart(14, '0');
},
epiMarket: () => this.pickRandomValue(['al', 'br']),
};
const repo = getModelAndRepository('Batch');
let batches = [] as { batchNumber: string; productCode: string }[];
if (repo) {
const { repository } = repo;
const query = (await repository.select().execute()) as Batch[];
if (query.length)
batches = query.map((item) => {
return {
batchNumber: item.batchNumber,
productCode: item.productCode,
};
});
}
const products = batches.map((b) => b.productCode);
let batches = await getBatchs();
data = (await this.generateData<Leaflet>()).map((item) => {
// item.epiMarket = this.pickRandomValue([... getMarkets().map(({value}) => value)]);
item.leafletType = this.pickRandomValue(LeafletType) as LeafletType;
Expand All @@ -144,17 +151,19 @@ export class FakerRepository<T extends Model> extends DecafFakerRepository<T> {
break;
}
case ProductStrength.name: {
const products = await getProducts();
this.limit = 2;
data = await this.generateData<ProductStrength>();
data = data.map((item: Partial<ProductStrength>, index: number) => {
item['productCode'] = index % 2 === 0 ? '00000000000012' : '00000000000013';
item['productCode'] =
index % 2 === 0 ? products[0].productCode : products[1].productCode;
item.substance = this.pickRandomValue(ProductNames);
return item as T;
}) as T[];
break;
}
default:
data = await this.generateData();
data = [];
}
try {
// data = await Promise.all(data.map(async (item: Partial<Model>) => {
Expand Down
7 changes: 7 additions & 0 deletions src/assets/i18n/ew/en.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
{
"errors": {
"gtin": {
"digits": "Gtin length is 14. you are missing {0} more digits.",
"checksum": "To be a valid gtin your next digit must be {0}",
"fallback": "Not a valid Gtin."
}
},
"component": {
"modal_diffs": {
"property": "Property",
Expand Down
Loading
Loading