diff --git a/client/packages/android/build_remote_server_libs.sh b/client/packages/android/build_remote_server_libs.sh index aad5855547..7e26d8ec76 100755 --- a/client/packages/android/build_remote_server_libs.sh +++ b/client/packages/android/build_remote_server_libs.sh @@ -7,7 +7,7 @@ set -e export AR=${NDK_BIN}/llvm-ar export CC_armv7_linux_androideabi=${NDK_BIN}/armv7a-linux-androideabi22-clang -# Build arm64-v8a:aarch64-linux-android and armeabi-v7a:armv7-linux-androideabi +# Build arm64-v8a:aarch64-linux-android (Defined in cargo.toml) PATH=PATH=$PATH:$NDK_BIN \ cargo build \ --release \ @@ -16,5 +16,4 @@ PATH=PATH=$PATH:$NDK_BIN \ --target-dir="$(pwd)/server-lib" # Copy built .so files to jniLib -cp "server-lib/aarch64-linux-android/release/libremote_server_android.so" "app/src/main/jniLibs/arm64-v8a/" -cp "server-lib/armv7-linux-androideabi/release/libremote_server_android.so" "app/src/main/jniLibs/armeabi-v7a/" \ No newline at end of file +cp "server-lib/aarch64-linux-android/release/libremote_server_android.so" "app/src/main/jniLibs/arm64-v8a/" \ No newline at end of file diff --git a/client/packages/common/src/intl/locales/en/common.json b/client/packages/common/src/intl/locales/en/common.json index dbf467b56c..623dd398bf 100644 --- a/client/packages/common/src/intl/locales/en/common.json +++ b/client/packages/common/src/intl/locales/en/common.json @@ -155,7 +155,7 @@ "description.fc-sell-price": "Foreign currency sell price per pack", "description.forecast-quantity": "Forecast Quantity to Reach Target", "description.initial-stock-on-hand": "Stock on hand on the first day of the program period", - "description.available-stock": "The customer's initial stock on hand + incoming stock + inventory adjustments - outgoing stock", + "description.available-stock": "The customer's initial stock on hand + incoming stock +/- inventory adjustments - outgoing stock", "description.invoice-number": "Shipment number", "description.last-reading-datetime": "Date and time of the last reading", "description.max-min-temperature": "Maximum or minimum temperature reading", @@ -231,6 +231,7 @@ "error.failed-to-create-prescription": "Failed to create prescription!", "error.failed-to-create-requisition": "Failed to create requisition! {{message}}", "error.failed-to-create-return": "Failed to create return!", + "error.failed-to-delete-item-variant": "Failed to delete item variant", "error.failed-to-generate-excel": "Failed to generate Excel", "error.failed-to-generate-report": "Failed to generate report", "error.failed-to-save-item-variant": "Failed to save item variant", @@ -474,8 +475,7 @@ "heading.user-sync": "User Synchronisation Status", "heading.username": "Username", "immunisations": "Immunizations", - "inbound-shipment": "Inbound Shipment", - "inbound-shipments": "Inbound Shipments", + "inbound-shipment": "Inbound Shipments", "indicators-demographics": "Demographics", "info.automatic-return": "This return was created automatically, as the result of an Supplier Return in another store.", "info.automatic-return-no-edit": "You are unable to edit details until the status is confirmed as Delivered.", @@ -736,6 +736,7 @@ "label.item_many": "Items", "label.item_one": "Item", "label.item_other": "Items", + "label.item-variant": "Item Variant", "label.items-expiring-before": "Items expiring before", "label.items-no-stock_few": "Items with no stock", "label.items-no-stock_many": "Items with no stock", @@ -1175,6 +1176,7 @@ "messages.confirm-delete-invoice-lines_many": "This will permanently remove {{count}} lines from this invoice", "messages.confirm-delete-invoice-lines_one": "This will permanently remove 1 line from this invoice", "messages.confirm-delete-invoice-lines_other": "This will permanently remove {{count}} lines from this invoice", + "messages.confirm-delete-item-variant": "This will permanently delete this item variant", "messages.confirm-delete-lines_few": "This will permanently remove {{count}} lines from this prescription", "messages.confirm-delete-lines_many": "This will permanently remove {{count}} lines from this prescription", "messages.confirm-delete-lines_one": "This will permanently remove 1 line from this prescription", @@ -1260,6 +1262,7 @@ "messages.deleted-generic_other": "Deleted {{count}} records", "messages.deleted-immunisation-programs_one": "Deleted {{count}} immunization program", "messages.deleted-immunisation-programs_other": "Deleted {{count}} immunization programs", + "messages.deleted-item-variant": "Item variant successfully deleted", "messages.deleted-lines_few": "Deleted {{count}} lines", "messages.deleted-lines_many": "Deleted {{count}} lines", "messages.deleted-lines_one": "Deleted {{count}} line", @@ -1453,7 +1456,7 @@ "messages.zero-return-quantity-will-delete-lines": "There are no return quantities specified. Click OK again to confirm and remove these lines from the return.", "monitoring": "Monitoring", "multiple": "[multiple]", - "outbound-shipment": "Outbound Shipment", + "outbound-shipment": "Outbound Shipments", "outbound-shipments": "Outbound Shipments", "patients": "Patients", "placeholder.enter-an-item-code-or-name": "Enter item code or name", @@ -1639,4 +1642,4 @@ "warning.caps-lock": "Warning: Caps lock is on", "warning.field-not-parsed": "{{field}} not parsed", "warning.nothing-to-supply": "Nothing left to supply!" -} \ No newline at end of file +} diff --git a/client/packages/common/src/intl/locales/es/common.json b/client/packages/common/src/intl/locales/es/common.json index 9d1d884498..71177c9169 100644 --- a/client/packages/common/src/intl/locales/es/common.json +++ b/client/packages/common/src/intl/locales/es/common.json @@ -369,7 +369,6 @@ "heading.username": "Nombre de usuario", "immunisations": "Inmunizaciones", "inbound-shipment": "Entradas", - "inbound-shipments": "Entradas", "indicators-demographics": "Demografía", "info.automatic-return": "Esta devolución se creó automáticamente como resultado de una Devolución de Proveedor en otro almacén.", "info.automatic-return-no-edit": "No puedes editar los detalles hasta que el estado se confirme como Entregado.", @@ -1363,4 +1362,4 @@ "label.breach-end": "Fin de la excursión", "label.breach-start": "Inicio de la excursión", "label.breaches": "Excursiones" -} \ No newline at end of file +} diff --git a/client/packages/common/src/intl/locales/fr/common.json b/client/packages/common/src/intl/locales/fr/common.json index b92aa2587e..cd52d7e1b8 100644 --- a/client/packages/common/src/intl/locales/fr/common.json +++ b/client/packages/common/src/intl/locales/fr/common.json @@ -451,7 +451,6 @@ "heading.username": "Nom d'utilisateur", "immunisations": "Vaccinations", "inbound-shipment": "Bon de livraison", - "inbound-shipments": "Bons de livraison", "indicators-demographics": "Données démographiques", "info.automatic-return": "Ce retour client a été créé automatiquement, suite à un retour fournisseur créé dans un autre dépôt.", "info.automatic-return-no-edit": "Vous ne pouvez modifier cette transaction que lorsque le statut est confirmé comme \"Livré(e)\"", @@ -1537,4 +1536,4 @@ "warning.caps-lock": "Avertissement: Verrouillage majuscule activé", "warning.field-not-parsed": "{{field}} non analysé", "warning.nothing-to-supply": "La quantité demandée à été fournie !" -} \ No newline at end of file +} diff --git a/client/packages/common/src/intl/locales/pt/common.json b/client/packages/common/src/intl/locales/pt/common.json index d9e55fd931..2ada76579c 100644 --- a/client/packages/common/src/intl/locales/pt/common.json +++ b/client/packages/common/src/intl/locales/pt/common.json @@ -332,7 +332,6 @@ "heading.user-sync": "Estado de sincronização de utilizador", "heading.username": "Nome do usuário", "inbound-shipment": "Entrada", - "inbound-shipments": "Entradas", "info.automatic-shipment": "Esta entrada foi criada automaticamente, como um resultado de um Envio de outro local.", "info.automatic-shipment-no-edit": "Você não pode editar os detalhes até que o status seja confirmado como Entregue.", "info.cannot-edit-program-requisition": "Não foi possível editar o fornecedor, mínimo MDE, máximo MDE ou adicionar itens a uma requisição do programa.", @@ -1159,4 +1158,4 @@ "error.no-customer-return-items": "Nenhum artigo foi adicionado a esse retorno.", "error.no-customer-returns": "Não há devoluções de Clientes para mostrar.", "error.no-immunisation-programs": "Não foram encontrados programas de imunização" -} \ No newline at end of file +} diff --git a/client/packages/common/src/intl/locales/ru/common.json b/client/packages/common/src/intl/locales/ru/common.json index ed89f348ec..8184820545 100644 --- a/client/packages/common/src/intl/locales/ru/common.json +++ b/client/packages/common/src/intl/locales/ru/common.json @@ -432,7 +432,6 @@ "heading.username": "Имя пользователя", "immunisations": "Прививки", "inbound-shipment": "Входящие Поставки", - "inbound-shipments": "Входящие Поставки", "indicators-demographics": "Демография", "info.automatic-return": "Этот возврат был создан автоматически в результате Исходящего Возврата в другом хранилище.", "info.automatic-return-no-edit": "Вы не можете редактировать детали, пока статус не будет подтвержден как Доставлено.", @@ -1498,4 +1497,4 @@ "messages.cant-return-shipment-replenishment": "Невозможно вернуть строки, пока статус не станет «Доставлено»", "messages.changing-max-mos": "Это изменит целевые месяцы запасов.", "messages.changing-min-mos": "Это изменит порог перезаказа на месяцы запасов." -} \ No newline at end of file +} diff --git a/client/packages/common/src/intl/locales/tet/common.json b/client/packages/common/src/intl/locales/tet/common.json index 60c68ae34a..0183a3645c 100644 --- a/client/packages/common/src/intl/locales/tet/common.json +++ b/client/packages/common/src/intl/locales/tet/common.json @@ -191,7 +191,6 @@ "heading.transport-details": "Detaliu Transportasaun", "heading.username": "Naran Ujuáriu", "inbound-shipment": "Remessa Sasan Tama", - "inbound-shipments": "Remessas sasan sira ne'ebe tama", "info.automatic-shipment": "Remessa ne'e kria Automatiku, tanba iha remessa Sasan Sai iha Store seluk. Ita labele hadia detaliu sira ne'e, antes estatus konfirmadu hanesan entrega tiha ona.", "info.manual-shipment": "Remessa ne'e kria ho manual. Status 'entrega tiha ona' sei la atualiza automatiku.", "info.no-shipment": "Finaliza requizisaun ida ne'e sei prevene ita husi kria remessa ida ba ida ne'e.", diff --git a/client/packages/common/src/types/schema.ts b/client/packages/common/src/types/schema.ts index 2c1a2dfde6..d3a46b3c3d 100644 --- a/client/packages/common/src/types/schema.ts +++ b/client/packages/common/src/types/schema.ts @@ -965,6 +965,17 @@ export type ClinicianSortInput = { export type CliniciansResponse = ClinicianConnector; +export type ColdStorageTypeConnector = { + __typename: 'ColdStorageTypeConnector'; + nodes: Array; + totalCount: Scalars['Int']['output']; +}; + +export type ColdStorageTypeFilterInput = { + id?: InputMaybe; + name?: InputMaybe; +}; + export type ColdStorageTypeNode = { __typename: 'ColdStorageTypeNode'; id: Scalars['String']['output']; @@ -973,6 +984,23 @@ export type ColdStorageTypeNode = { name: Scalars['String']['output']; }; +export enum ColdStorageTypeSortFieldInput { + Id = 'id', + Name = 'name' +} + +export type ColdStorageTypeSortInput = { + /** + * Sort query result is sorted descending or ascending (if not provided the default is + * ascending) + */ + desc?: InputMaybe; + /** Sort query result by `key` */ + key: ColdStorageTypeSortFieldInput; +}; + +export type ColdStorageTypesResponse = ColdStorageTypeConnector; + export type ConfigureNamePropertiesResponse = Success; export type ConfigureNamePropertyInput = { @@ -1191,6 +1219,7 @@ export type CustomerReturnLineNode = { item: ItemNode; itemCode: Scalars['String']['output']; itemName: Scalars['String']['output']; + itemVariantId?: Maybe; note?: Maybe; numberOfPacksIssued?: Maybe; numberOfPacksReturned: Scalars['Float']['output']; @@ -3202,6 +3231,7 @@ export type InvoiceLineNode = { itemCode: Scalars['String']['output']; itemId: Scalars['String']['output']; itemName: Scalars['String']['output']; + itemVariantId?: Maybe; location?: Maybe; locationId?: Maybe; locationName?: Maybe; @@ -3541,6 +3571,7 @@ export type ItemVariantMutationsUpsertItemVariantArgs = { export type ItemVariantNode = { __typename: 'ItemVariantNode'; + coldStorageType?: Maybe; coldStorageTypeId?: Maybe; dosesPerUnit?: Maybe; id: Scalars['String']['output']; @@ -4662,6 +4693,8 @@ export type NameFilterInput = { isCustomer?: InputMaybe; /** Filter by donor property */ isDonor?: InputMaybe; + /** Filter by manufacturer property */ + isManufacturer?: InputMaybe; /** Is this name a store */ isStore?: InputMaybe; /** Filter by supplier property */ @@ -5369,6 +5402,8 @@ export type Queries = { barcodeByGtin: BarcodeResponse; centralPatientSearch: CentralPatientSearchResponse; clinicians: CliniciansResponse; + /** Query omSupply "cold_storage_type" entries */ + coldStorageTypes: ColdStorageTypesResponse; contactTraces: ContactTraceResponse; currencies: CurrenciesResponse; customerProgramRequisitionSettings: Array; @@ -5620,6 +5655,14 @@ export type QueriesCliniciansArgs = { }; +export type QueriesColdStorageTypesArgs = { + filter?: InputMaybe; + page?: InputMaybe; + sort?: InputMaybe>; + storeId: Scalars['String']['input']; +}; + + export type QueriesContactTracesArgs = { filter?: InputMaybe; page?: InputMaybe; @@ -6863,6 +6906,7 @@ export type StockLineNode = { item: ItemNode; itemId: Scalars['String']['output']; itemName: Scalars['String']['output']; + itemVariantId?: Maybe; location?: Maybe; locationId?: Maybe; locationName?: Maybe; @@ -7634,7 +7678,7 @@ export type UpdateInboundShipmentLineInput = { expiryDate?: InputMaybe; id: Scalars['String']['input']; itemId?: InputMaybe; - itemVariantId?: InputMaybe; + itemVariantId?: InputMaybe; location?: InputMaybe; numberOfPacks?: InputMaybe; packSize?: InputMaybe; @@ -7708,6 +7752,7 @@ export type UpdateLocationErrorInterface = { export type UpdateLocationInput = { code?: InputMaybe; + coldStorageTypeId?: InputMaybe; id: Scalars['String']['input']; name?: InputMaybe; onHold?: InputMaybe; @@ -8147,6 +8192,7 @@ export type UpdateStockLineInput = { costPricePerPack?: InputMaybe; expiryDate?: InputMaybe; id: Scalars['String']['input']; + itemVariantId?: InputMaybe; location?: InputMaybe; onHold?: InputMaybe; sellPricePerPack?: InputMaybe; diff --git a/client/packages/common/src/utils/environment/EnvUtils.ts b/client/packages/common/src/utils/environment/EnvUtils.ts index 2643a948f9..4dd0bb5dd9 100644 --- a/client/packages/common/src/utils/environment/EnvUtils.ts +++ b/client/packages/common/src/utils/environment/EnvUtils.ts @@ -32,7 +32,7 @@ const mapRoute = (route: string): RouteMapping => { return { title: 'customers', docs: '/distribution/customers/' }; case inRoute(AppRoute.InboundShipment): return { - title: 'inbound-shipments', + title: 'inbound-shipment', docs: '/replenishment/inbound-shipments/', }; case inRoute(AppRoute.SupplierReturn): diff --git a/client/packages/dashboard/src/widgets/ReplenishmentWidget.tsx b/client/packages/dashboard/src/widgets/ReplenishmentWidget.tsx index 54e853da86..f3a173ae85 100644 --- a/client/packages/dashboard/src/widgets/ReplenishmentWidget.tsx +++ b/client/packages/dashboard/src/widgets/ReplenishmentWidget.tsx @@ -116,7 +116,7 @@ export const ReplenishmentWidget: React.FC = () => { error={error as ApiException} isError={isError} isLoading={isLoading} - title={t('inbound-shipments', { ns: 'app' })} + title={t('inbound-shipment', { ns: 'app' })} stats={[ { label: t('label.today', { ns: 'dashboard' }), diff --git a/client/packages/invoices/src/InboundShipment/DetailView/modals/InboundLineEdit/TabTables.tsx b/client/packages/invoices/src/InboundShipment/DetailView/modals/InboundLineEdit/TabTables.tsx index a5ace0866e..a8f362ace7 100644 --- a/client/packages/invoices/src/InboundShipment/DetailView/modals/InboundLineEdit/TabTables.tsx +++ b/client/packages/invoices/src/InboundShipment/DetailView/modals/InboundLineEdit/TabTables.tsx @@ -23,6 +23,7 @@ import { DraftInboundLine } from '../../../../types'; import { CurrencyRowFragment, getLocationInputColumn, + ItemVariantInputCell, LocationRowFragment, PackSizeEntryCell, } from '@openmsupply-client/system'; @@ -93,6 +94,16 @@ export const QuantityTableComponent: FC = ({ [ getBatchColumn(updateDraftLine, theme), getExpiryColumn(updateDraftLine, theme), + + { + key: 'itemVariantId', + label: 'label.item-variant', + width: 170, + Cell: props => ( + + ), + setter: updateDraftLine, + }, [ 'numberOfPacks', { diff --git a/client/packages/invoices/src/InboundShipment/api/api.ts b/client/packages/invoices/src/InboundShipment/api/api.ts index 05393ab408..3e70745735 100644 --- a/client/packages/invoices/src/InboundShipment/api/api.ts +++ b/client/packages/invoices/src/InboundShipment/api/api.ts @@ -104,6 +104,7 @@ const inboundParsers = { numberOfPacks: line.numberOfPacks, invoiceId: line.invoiceId, location: setNullableInput('id', line.location), + itemVariantId: line.itemVariantId, }; }, toUpdateLine: (line: DraftInboundLine): UpdateInboundShipmentLineInput => ({ @@ -118,6 +119,9 @@ const inboundParsers = { packSize: line.packSize, numberOfPacks: line.numberOfPacks, location: setNullableInput('id', line.location), + itemVariantId: setNullableInput('itemVariantId', { + itemVariantId: line.itemVariantId, + }), }), toDeleteLine: (line: { id: string }): DeleteInboundShipmentLineInput => { return { id: line.id }; diff --git a/client/packages/invoices/src/InboundShipment/api/operations.generated.ts b/client/packages/invoices/src/InboundShipment/api/operations.generated.ts index 014ffa3f04..5d7dfa1a09 100644 --- a/client/packages/invoices/src/InboundShipment/api/operations.generated.ts +++ b/client/packages/invoices/src/InboundShipment/api/operations.generated.ts @@ -3,9 +3,9 @@ import * as Types from '@openmsupply-client/common'; import { GraphQLClient, RequestOptions } from 'graphql-request'; import gql from 'graphql-tag'; type GraphQLClientRequestHeaders = RequestOptions['requestHeaders']; -export type InboundLineFragment = { __typename: 'InvoiceLineNode', id: string, type: Types.InvoiceLineNodeType, batch?: string | null, costPricePerPack: number, sellPricePerPack: number, expiryDate?: string | null, numberOfPacks: number, packSize: number, note?: string | null, invoiceId: string, totalBeforeTax: number, totalAfterTax: number, taxPercentage?: number | null, foreignCurrencyPriceBeforeTax?: number | null, itemName: string, item: { __typename: 'ItemNode', id: string, name: string, code: string, unitName?: string | null, defaultPackSize: number }, location?: { __typename: 'LocationNode', name: string, id: string, code: string, onHold: boolean } | null, stockLine?: { __typename: 'StockLineNode', availableNumberOfPacks: number, batch?: string | null, costPricePerPack: number, expiryDate?: string | null, id: string, itemId: string, packSize: number, sellPricePerPack: number, storeId: string, totalNumberOfPacks: number, onHold: boolean, note?: string | null } | null }; +export type InboundLineFragment = { __typename: 'InvoiceLineNode', id: string, type: Types.InvoiceLineNodeType, batch?: string | null, costPricePerPack: number, sellPricePerPack: number, expiryDate?: string | null, numberOfPacks: number, packSize: number, note?: string | null, invoiceId: string, totalBeforeTax: number, totalAfterTax: number, taxPercentage?: number | null, foreignCurrencyPriceBeforeTax?: number | null, itemName: string, itemVariantId?: string | null, item: { __typename: 'ItemNode', id: string, name: string, code: string, unitName?: string | null, defaultPackSize: number }, location?: { __typename: 'LocationNode', name: string, id: string, code: string, onHold: boolean } | null, stockLine?: { __typename: 'StockLineNode', availableNumberOfPacks: number, batch?: string | null, costPricePerPack: number, expiryDate?: string | null, id: string, itemId: string, packSize: number, sellPricePerPack: number, storeId: string, totalNumberOfPacks: number, onHold: boolean, note?: string | null } | null }; -export type InboundFragment = { __typename: 'InvoiceNode', id: string, comment?: string | null, createdDatetime: string, allocatedDatetime?: string | null, deliveredDatetime?: string | null, pickedDatetime?: string | null, shippedDatetime?: string | null, verifiedDatetime?: string | null, invoiceNumber: number, colour?: string | null, onHold: boolean, otherPartyId: string, otherPartyName: string, status: Types.InvoiceNodeStatus, theirReference?: string | null, transportReference?: string | null, type: Types.InvoiceNodeType, taxPercentage?: number | null, currencyRate: number, linkedShipment?: { __typename: 'InvoiceNode', id: string } | null, user?: { __typename: 'UserNode', username: string, email?: string | null } | null, requisition?: { __typename: 'RequisitionNode', id: string, requisitionNumber: number, createdDatetime: string, user?: { __typename: 'UserNode', username: string } | null } | null, lines: { __typename: 'InvoiceLineConnector', totalCount: number, nodes: Array<{ __typename: 'InvoiceLineNode', id: string, type: Types.InvoiceLineNodeType, batch?: string | null, costPricePerPack: number, sellPricePerPack: number, expiryDate?: string | null, numberOfPacks: number, packSize: number, note?: string | null, invoiceId: string, totalBeforeTax: number, totalAfterTax: number, taxPercentage?: number | null, foreignCurrencyPriceBeforeTax?: number | null, itemName: string, item: { __typename: 'ItemNode', id: string, name: string, code: string, unitName?: string | null, defaultPackSize: number }, location?: { __typename: 'LocationNode', name: string, id: string, code: string, onHold: boolean } | null, stockLine?: { __typename: 'StockLineNode', availableNumberOfPacks: number, batch?: string | null, costPricePerPack: number, expiryDate?: string | null, id: string, itemId: string, packSize: number, sellPricePerPack: number, storeId: string, totalNumberOfPacks: number, onHold: boolean, note?: string | null } | null }> }, otherParty: { __typename: 'NameNode', id: string, name: string, code: string, isCustomer: boolean, isSupplier: boolean, isOnHold: boolean, store?: { __typename: 'StoreNode', id: string, code: string } | null }, pricing: { __typename: 'PricingNode', totalAfterTax: number, totalBeforeTax: number, stockTotalBeforeTax: number, stockTotalAfterTax: number, serviceTotalAfterTax: number, serviceTotalBeforeTax: number, taxPercentage?: number | null, foreignCurrencyTotalAfterTax?: number | null }, currency?: { __typename: 'CurrencyNode', id: string, code: string, rate: number, isHomeCurrency: boolean } | null }; +export type InboundFragment = { __typename: 'InvoiceNode', id: string, comment?: string | null, createdDatetime: string, allocatedDatetime?: string | null, deliveredDatetime?: string | null, pickedDatetime?: string | null, shippedDatetime?: string | null, verifiedDatetime?: string | null, invoiceNumber: number, colour?: string | null, onHold: boolean, otherPartyId: string, otherPartyName: string, status: Types.InvoiceNodeStatus, theirReference?: string | null, transportReference?: string | null, type: Types.InvoiceNodeType, taxPercentage?: number | null, currencyRate: number, linkedShipment?: { __typename: 'InvoiceNode', id: string } | null, user?: { __typename: 'UserNode', username: string, email?: string | null } | null, requisition?: { __typename: 'RequisitionNode', id: string, requisitionNumber: number, createdDatetime: string, user?: { __typename: 'UserNode', username: string } | null } | null, lines: { __typename: 'InvoiceLineConnector', totalCount: number, nodes: Array<{ __typename: 'InvoiceLineNode', id: string, type: Types.InvoiceLineNodeType, batch?: string | null, costPricePerPack: number, sellPricePerPack: number, expiryDate?: string | null, numberOfPacks: number, packSize: number, note?: string | null, invoiceId: string, totalBeforeTax: number, totalAfterTax: number, taxPercentage?: number | null, foreignCurrencyPriceBeforeTax?: number | null, itemName: string, itemVariantId?: string | null, item: { __typename: 'ItemNode', id: string, name: string, code: string, unitName?: string | null, defaultPackSize: number }, location?: { __typename: 'LocationNode', name: string, id: string, code: string, onHold: boolean } | null, stockLine?: { __typename: 'StockLineNode', availableNumberOfPacks: number, batch?: string | null, costPricePerPack: number, expiryDate?: string | null, id: string, itemId: string, packSize: number, sellPricePerPack: number, storeId: string, totalNumberOfPacks: number, onHold: boolean, note?: string | null } | null }> }, otherParty: { __typename: 'NameNode', id: string, name: string, code: string, isCustomer: boolean, isSupplier: boolean, isOnHold: boolean, store?: { __typename: 'StoreNode', id: string, code: string } | null }, pricing: { __typename: 'PricingNode', totalAfterTax: number, totalBeforeTax: number, stockTotalBeforeTax: number, stockTotalAfterTax: number, serviceTotalAfterTax: number, serviceTotalBeforeTax: number, taxPercentage?: number | null, foreignCurrencyTotalAfterTax?: number | null }, currency?: { __typename: 'CurrencyNode', id: string, code: string, rate: number, isHomeCurrency: boolean } | null }; export type InboundRowFragment = { __typename: 'InvoiceNode', comment?: string | null, createdDatetime: string, deliveredDatetime?: string | null, id: string, invoiceNumber: number, otherPartyName: string, status: Types.InvoiceNodeStatus, colour?: string | null, theirReference?: string | null, taxPercentage?: number | null, onHold: boolean, currencyRate: number, pricing: { __typename: 'PricingNode', totalAfterTax: number, taxPercentage?: number | null, foreignCurrencyTotalAfterTax?: number | null }, linkedShipment?: { __typename: 'InvoiceNode', id: string } | null, currency?: { __typename: 'CurrencyNode', id: string, code: string, rate: number, isHomeCurrency: boolean } | null }; @@ -27,7 +27,7 @@ export type InvoiceQueryVariables = Types.Exact<{ }>; -export type InvoiceQuery = { __typename: 'Queries', invoice: { __typename: 'InvoiceNode', id: string, comment?: string | null, createdDatetime: string, allocatedDatetime?: string | null, deliveredDatetime?: string | null, pickedDatetime?: string | null, shippedDatetime?: string | null, verifiedDatetime?: string | null, invoiceNumber: number, colour?: string | null, onHold: boolean, otherPartyId: string, otherPartyName: string, status: Types.InvoiceNodeStatus, theirReference?: string | null, transportReference?: string | null, type: Types.InvoiceNodeType, taxPercentage?: number | null, currencyRate: number, linkedShipment?: { __typename: 'InvoiceNode', id: string } | null, user?: { __typename: 'UserNode', username: string, email?: string | null } | null, requisition?: { __typename: 'RequisitionNode', id: string, requisitionNumber: number, createdDatetime: string, user?: { __typename: 'UserNode', username: string } | null } | null, lines: { __typename: 'InvoiceLineConnector', totalCount: number, nodes: Array<{ __typename: 'InvoiceLineNode', id: string, type: Types.InvoiceLineNodeType, batch?: string | null, costPricePerPack: number, sellPricePerPack: number, expiryDate?: string | null, numberOfPacks: number, packSize: number, note?: string | null, invoiceId: string, totalBeforeTax: number, totalAfterTax: number, taxPercentage?: number | null, foreignCurrencyPriceBeforeTax?: number | null, itemName: string, item: { __typename: 'ItemNode', id: string, name: string, code: string, unitName?: string | null, defaultPackSize: number }, location?: { __typename: 'LocationNode', name: string, id: string, code: string, onHold: boolean } | null, stockLine?: { __typename: 'StockLineNode', availableNumberOfPacks: number, batch?: string | null, costPricePerPack: number, expiryDate?: string | null, id: string, itemId: string, packSize: number, sellPricePerPack: number, storeId: string, totalNumberOfPacks: number, onHold: boolean, note?: string | null } | null }> }, otherParty: { __typename: 'NameNode', id: string, name: string, code: string, isCustomer: boolean, isSupplier: boolean, isOnHold: boolean, store?: { __typename: 'StoreNode', id: string, code: string } | null }, pricing: { __typename: 'PricingNode', totalAfterTax: number, totalBeforeTax: number, stockTotalBeforeTax: number, stockTotalAfterTax: number, serviceTotalAfterTax: number, serviceTotalBeforeTax: number, taxPercentage?: number | null, foreignCurrencyTotalAfterTax?: number | null }, currency?: { __typename: 'CurrencyNode', id: string, code: string, rate: number, isHomeCurrency: boolean } | null } | { __typename: 'NodeError', error: { __typename: 'DatabaseError', description: string, fullError: string } | { __typename: 'RecordNotFound', description: string } } }; +export type InvoiceQuery = { __typename: 'Queries', invoice: { __typename: 'InvoiceNode', id: string, comment?: string | null, createdDatetime: string, allocatedDatetime?: string | null, deliveredDatetime?: string | null, pickedDatetime?: string | null, shippedDatetime?: string | null, verifiedDatetime?: string | null, invoiceNumber: number, colour?: string | null, onHold: boolean, otherPartyId: string, otherPartyName: string, status: Types.InvoiceNodeStatus, theirReference?: string | null, transportReference?: string | null, type: Types.InvoiceNodeType, taxPercentage?: number | null, currencyRate: number, linkedShipment?: { __typename: 'InvoiceNode', id: string } | null, user?: { __typename: 'UserNode', username: string, email?: string | null } | null, requisition?: { __typename: 'RequisitionNode', id: string, requisitionNumber: number, createdDatetime: string, user?: { __typename: 'UserNode', username: string } | null } | null, lines: { __typename: 'InvoiceLineConnector', totalCount: number, nodes: Array<{ __typename: 'InvoiceLineNode', id: string, type: Types.InvoiceLineNodeType, batch?: string | null, costPricePerPack: number, sellPricePerPack: number, expiryDate?: string | null, numberOfPacks: number, packSize: number, note?: string | null, invoiceId: string, totalBeforeTax: number, totalAfterTax: number, taxPercentage?: number | null, foreignCurrencyPriceBeforeTax?: number | null, itemName: string, itemVariantId?: string | null, item: { __typename: 'ItemNode', id: string, name: string, code: string, unitName?: string | null, defaultPackSize: number }, location?: { __typename: 'LocationNode', name: string, id: string, code: string, onHold: boolean } | null, stockLine?: { __typename: 'StockLineNode', availableNumberOfPacks: number, batch?: string | null, costPricePerPack: number, expiryDate?: string | null, id: string, itemId: string, packSize: number, sellPricePerPack: number, storeId: string, totalNumberOfPacks: number, onHold: boolean, note?: string | null } | null }> }, otherParty: { __typename: 'NameNode', id: string, name: string, code: string, isCustomer: boolean, isSupplier: boolean, isOnHold: boolean, store?: { __typename: 'StoreNode', id: string, code: string } | null }, pricing: { __typename: 'PricingNode', totalAfterTax: number, totalBeforeTax: number, stockTotalBeforeTax: number, stockTotalAfterTax: number, serviceTotalAfterTax: number, serviceTotalBeforeTax: number, taxPercentage?: number | null, foreignCurrencyTotalAfterTax?: number | null }, currency?: { __typename: 'CurrencyNode', id: string, code: string, rate: number, isHomeCurrency: boolean } | null } | { __typename: 'NodeError', error: { __typename: 'DatabaseError', description: string, fullError: string } | { __typename: 'RecordNotFound', description: string } } }; export type InboundByNumberQueryVariables = Types.Exact<{ invoiceNumber: Types.Scalars['Int']['input']; @@ -35,7 +35,7 @@ export type InboundByNumberQueryVariables = Types.Exact<{ }>; -export type InboundByNumberQuery = { __typename: 'Queries', invoiceByNumber: { __typename: 'InvoiceNode', id: string, comment?: string | null, createdDatetime: string, allocatedDatetime?: string | null, deliveredDatetime?: string | null, pickedDatetime?: string | null, shippedDatetime?: string | null, verifiedDatetime?: string | null, invoiceNumber: number, colour?: string | null, onHold: boolean, otherPartyId: string, otherPartyName: string, status: Types.InvoiceNodeStatus, theirReference?: string | null, transportReference?: string | null, type: Types.InvoiceNodeType, taxPercentage?: number | null, currencyRate: number, linkedShipment?: { __typename: 'InvoiceNode', id: string } | null, user?: { __typename: 'UserNode', username: string, email?: string | null } | null, requisition?: { __typename: 'RequisitionNode', id: string, requisitionNumber: number, createdDatetime: string, user?: { __typename: 'UserNode', username: string } | null } | null, lines: { __typename: 'InvoiceLineConnector', totalCount: number, nodes: Array<{ __typename: 'InvoiceLineNode', id: string, type: Types.InvoiceLineNodeType, batch?: string | null, costPricePerPack: number, sellPricePerPack: number, expiryDate?: string | null, numberOfPacks: number, packSize: number, note?: string | null, invoiceId: string, totalBeforeTax: number, totalAfterTax: number, taxPercentage?: number | null, foreignCurrencyPriceBeforeTax?: number | null, itemName: string, item: { __typename: 'ItemNode', id: string, name: string, code: string, unitName?: string | null, defaultPackSize: number }, location?: { __typename: 'LocationNode', name: string, id: string, code: string, onHold: boolean } | null, stockLine?: { __typename: 'StockLineNode', availableNumberOfPacks: number, batch?: string | null, costPricePerPack: number, expiryDate?: string | null, id: string, itemId: string, packSize: number, sellPricePerPack: number, storeId: string, totalNumberOfPacks: number, onHold: boolean, note?: string | null } | null }> }, otherParty: { __typename: 'NameNode', id: string, name: string, code: string, isCustomer: boolean, isSupplier: boolean, isOnHold: boolean, store?: { __typename: 'StoreNode', id: string, code: string } | null }, pricing: { __typename: 'PricingNode', totalAfterTax: number, totalBeforeTax: number, stockTotalBeforeTax: number, stockTotalAfterTax: number, serviceTotalAfterTax: number, serviceTotalBeforeTax: number, taxPercentage?: number | null, foreignCurrencyTotalAfterTax?: number | null }, currency?: { __typename: 'CurrencyNode', id: string, code: string, rate: number, isHomeCurrency: boolean } | null } | { __typename: 'NodeError', error: { __typename: 'DatabaseError', description: string, fullError: string } | { __typename: 'RecordNotFound', description: string } } }; +export type InboundByNumberQuery = { __typename: 'Queries', invoiceByNumber: { __typename: 'InvoiceNode', id: string, comment?: string | null, createdDatetime: string, allocatedDatetime?: string | null, deliveredDatetime?: string | null, pickedDatetime?: string | null, shippedDatetime?: string | null, verifiedDatetime?: string | null, invoiceNumber: number, colour?: string | null, onHold: boolean, otherPartyId: string, otherPartyName: string, status: Types.InvoiceNodeStatus, theirReference?: string | null, transportReference?: string | null, type: Types.InvoiceNodeType, taxPercentage?: number | null, currencyRate: number, linkedShipment?: { __typename: 'InvoiceNode', id: string } | null, user?: { __typename: 'UserNode', username: string, email?: string | null } | null, requisition?: { __typename: 'RequisitionNode', id: string, requisitionNumber: number, createdDatetime: string, user?: { __typename: 'UserNode', username: string } | null } | null, lines: { __typename: 'InvoiceLineConnector', totalCount: number, nodes: Array<{ __typename: 'InvoiceLineNode', id: string, type: Types.InvoiceLineNodeType, batch?: string | null, costPricePerPack: number, sellPricePerPack: number, expiryDate?: string | null, numberOfPacks: number, packSize: number, note?: string | null, invoiceId: string, totalBeforeTax: number, totalAfterTax: number, taxPercentage?: number | null, foreignCurrencyPriceBeforeTax?: number | null, itemName: string, itemVariantId?: string | null, item: { __typename: 'ItemNode', id: string, name: string, code: string, unitName?: string | null, defaultPackSize: number }, location?: { __typename: 'LocationNode', name: string, id: string, code: string, onHold: boolean } | null, stockLine?: { __typename: 'StockLineNode', availableNumberOfPacks: number, batch?: string | null, costPricePerPack: number, expiryDate?: string | null, id: string, itemId: string, packSize: number, sellPricePerPack: number, storeId: string, totalNumberOfPacks: number, onHold: boolean, note?: string | null } | null }> }, otherParty: { __typename: 'NameNode', id: string, name: string, code: string, isCustomer: boolean, isSupplier: boolean, isOnHold: boolean, store?: { __typename: 'StoreNode', id: string, code: string } | null }, pricing: { __typename: 'PricingNode', totalAfterTax: number, totalBeforeTax: number, stockTotalBeforeTax: number, stockTotalAfterTax: number, serviceTotalAfterTax: number, serviceTotalBeforeTax: number, taxPercentage?: number | null, foreignCurrencyTotalAfterTax?: number | null }, currency?: { __typename: 'CurrencyNode', id: string, code: string, rate: number, isHomeCurrency: boolean } | null } | { __typename: 'NodeError', error: { __typename: 'DatabaseError', description: string, fullError: string } | { __typename: 'RecordNotFound', description: string } } }; export type UpdateInboundShipmentMutationVariables = Types.Exact<{ storeId: Types.Scalars['String']['input']; @@ -106,6 +106,7 @@ export const InboundLineFragmentDoc = gql` taxPercentage foreignCurrencyPriceBeforeTax itemName + itemVariantId item { __typename id diff --git a/client/packages/invoices/src/InboundShipment/api/operations.graphql b/client/packages/invoices/src/InboundShipment/api/operations.graphql index decffbb2c0..38b2128eed 100644 --- a/client/packages/invoices/src/InboundShipment/api/operations.graphql +++ b/client/packages/invoices/src/InboundShipment/api/operations.graphql @@ -16,6 +16,7 @@ fragment InboundLine on InvoiceLineNode { taxPercentage foreignCurrencyPriceBeforeTax itemName + itemVariantId item { __typename diff --git a/client/packages/invoices/src/Returns/api/operations.generated.ts b/client/packages/invoices/src/Returns/api/operations.generated.ts index e37631da3b..4a642e6baa 100644 --- a/client/packages/invoices/src/Returns/api/operations.generated.ts +++ b/client/packages/invoices/src/Returns/api/operations.generated.ts @@ -49,7 +49,7 @@ export type GenerateSupplierReturnLinesQueryVariables = Types.Exact<{ export type GenerateSupplierReturnLinesQuery = { __typename: 'Queries', generateSupplierReturnLines: { __typename: 'SupplierReturnLineConnector', nodes: Array<{ __typename: 'SupplierReturnLineNode', availableNumberOfPacks: number, batch?: string | null, expiryDate?: string | null, id: string, numberOfPacksToReturn: number, packSize: number, stockLineId: string, note?: string | null, reasonId?: string | null, itemName: string, itemCode: string, item: { __typename: 'ItemNode', id: string, unitName?: string | null } }> } }; -export type GenerateCustomerReturnLineFragment = { __typename: 'CustomerReturnLineNode', batch?: string | null, expiryDate?: string | null, id: string, packSize: number, stockLineId?: string | null, numberOfPacksReturned: number, numberOfPacksIssued?: number | null, note?: string | null, reasonId?: string | null, itemName: string, itemCode: string, item: { __typename: 'ItemNode', id: string, unitName?: string | null, code: string, name: string } }; +export type GenerateCustomerReturnLineFragment = { __typename: 'CustomerReturnLineNode', batch?: string | null, expiryDate?: string | null, id: string, packSize: number, stockLineId?: string | null, numberOfPacksReturned: number, numberOfPacksIssued?: number | null, note?: string | null, reasonId?: string | null, itemName: string, itemCode: string, itemVariantId?: string | null, item: { __typename: 'ItemNode', id: string, unitName?: string | null, code: string, name: string } }; export type GenerateCustomerReturnLinesQueryVariables = Types.Exact<{ input: Types.GenerateCustomerReturnLinesInput; @@ -57,7 +57,7 @@ export type GenerateCustomerReturnLinesQueryVariables = Types.Exact<{ }>; -export type GenerateCustomerReturnLinesQuery = { __typename: 'Queries', generateCustomerReturnLines: { __typename: 'GeneratedCustomerReturnLineConnector', nodes: Array<{ __typename: 'CustomerReturnLineNode', batch?: string | null, expiryDate?: string | null, id: string, packSize: number, stockLineId?: string | null, numberOfPacksReturned: number, numberOfPacksIssued?: number | null, note?: string | null, reasonId?: string | null, itemName: string, itemCode: string, item: { __typename: 'ItemNode', id: string, unitName?: string | null, code: string, name: string } }> } }; +export type GenerateCustomerReturnLinesQuery = { __typename: 'Queries', generateCustomerReturnLines: { __typename: 'GeneratedCustomerReturnLineConnector', nodes: Array<{ __typename: 'CustomerReturnLineNode', batch?: string | null, expiryDate?: string | null, id: string, packSize: number, stockLineId?: string | null, numberOfPacksReturned: number, numberOfPacksIssued?: number | null, note?: string | null, reasonId?: string | null, itemName: string, itemCode: string, itemVariantId?: string | null, item: { __typename: 'ItemNode', id: string, unitName?: string | null, code: string, name: string } }> } }; export type SupplierReturnByNumberQueryVariables = Types.Exact<{ invoiceNumber: Types.Scalars['Int']['input']; @@ -352,6 +352,7 @@ export const GenerateCustomerReturnLineFragmentDoc = gql` reasonId itemName itemCode + itemVariantId item { id unitName diff --git a/client/packages/invoices/src/Returns/api/operations.graphql b/client/packages/invoices/src/Returns/api/operations.graphql index ad29767239..99195218f4 100644 --- a/client/packages/invoices/src/Returns/api/operations.graphql +++ b/client/packages/invoices/src/Returns/api/operations.graphql @@ -267,6 +267,7 @@ fragment GenerateCustomerReturnLine on CustomerReturnLineNode { reasonId itemName itemCode + itemVariantId item { id unitName diff --git a/client/packages/invoices/src/Returns/modals/CustomerReturn/ReturnQuantitiesTable.tsx b/client/packages/invoices/src/Returns/modals/CustomerReturn/ReturnQuantitiesTable.tsx index 1780367835..ae59b9d3b2 100644 --- a/client/packages/invoices/src/Returns/modals/CustomerReturn/ReturnQuantitiesTable.tsx +++ b/client/packages/invoices/src/Returns/modals/CustomerReturn/ReturnQuantitiesTable.tsx @@ -10,6 +10,7 @@ import { getExpiryDateInputColumn, useColumns, } from '@openmsupply-client/common'; +import { ItemVariantInputCell } from '@openmsupply-client/system'; import React from 'react'; import { GenerateCustomerReturnLineFragment } from '../../api'; import { PackSizeEntryCell } from '@openmsupply-client/system'; @@ -29,6 +30,16 @@ export const QuantityReturnedTableComponent = ({ [ 'itemCode', 'itemName', + { + key: 'itemVariantId', + label: 'label.item-variant', + width: 170, + setter: updateLine, + Cell: props => ( + + ), + getIsDisabled: () => isDisabled, + }, [ 'batch', { diff --git a/client/packages/invoices/src/Returns/modals/CustomerReturn/useDraftCustomerReturnLines.ts b/client/packages/invoices/src/Returns/modals/CustomerReturn/useDraftCustomerReturnLines.ts index 92185f9c49..5cc85e767f 100644 --- a/client/packages/invoices/src/Returns/modals/CustomerReturn/useDraftCustomerReturnLines.ts +++ b/client/packages/invoices/src/Returns/modals/CustomerReturn/useDraftCustomerReturnLines.ts @@ -96,6 +96,7 @@ export const useDraftCustomerReturnLines = ({ packSize, batch, expiryDate, + itemVariantId, }) => { return { id, @@ -106,6 +107,7 @@ export const useDraftCustomerReturnLines = ({ reasonId, note, numberOfPacksReturned, + itemVariantId, }; } ); diff --git a/client/packages/requisitions/src/ResponseRequisition/DetailView/columns.ts b/client/packages/requisitions/src/ResponseRequisition/DetailView/columns.ts index 4e17c5fa38..4f279dadd0 100644 --- a/client/packages/requisitions/src/ResponseRequisition/DetailView/columns.ts +++ b/client/packages/requisitions/src/ResponseRequisition/DetailView/columns.ts @@ -17,10 +17,7 @@ export const useResponseColumns = () => { queryParams: { sortBy }, } = useUrlQueryParams({ initialSort: { key: 'itemName', dir: 'asc' } }); const { isRemoteAuthorisation } = useResponse.utils.isRemoteAuthorisation(); - const { programName, linkedRequisition } = useResponse.document.fields([ - 'programName', - 'linkedRequisition', - ]); + const { programName } = useResponse.document.fields(['programName']); const columnDefinitions: ColumnDescription[] = [ getCommentPopoverColumn(), @@ -70,15 +67,8 @@ export const useResponseColumns = () => { width: 100, align: ColumnAlign.Right, Cell: PackQuantityCell, - getSortValue: rowData => - linkedRequisition - ? (rowData.linkedRequisitionLine?.itemStats?.availableStockOnHand ?? - 0) - : rowData.availableStockOnHand, - accessor: ({ rowData }) => - linkedRequisition - ? rowData.linkedRequisitionLine?.itemStats?.availableStockOnHand - : rowData.availableStockOnHand, + getSortValue: rowData => rowData.availableStockOnHand, + accessor: ({ rowData }) => rowData.availableStockOnHand, }); } columnDefinitions.push( @@ -138,9 +128,7 @@ export const useResponseColumns = () => { description: 'description.available-stock', Cell: PackQuantityCell, accessor: ({ rowData }) => { - const stockOnHand = linkedRequisition - ? (rowData.linkedRequisitionLine?.itemStats.availableStockOnHand ?? 0) - : rowData.availableStockOnHand; + const stockOnHand = rowData.initialStockOnHandUnits; const incomingStock = rowData.incomingUnits + rowData.additionInUnits; const outgoingStock = rowData.lossInUnits + rowData.outgoingUnits; @@ -173,11 +161,7 @@ export const useResponseColumns = () => { align: ColumnAlign.Right, sortable: false, Cell: PackQuantityCell, - accessor: ({ rowData }) => - linkedRequisition - ? (rowData.linkedRequisitionLine?.itemStats - .averageMonthlyConsumption ?? 0) - : rowData.averageMonthlyConsumption, + accessor: ({ rowData }) => rowData.averageMonthlyConsumption, }, { key: 'mos', @@ -187,18 +171,13 @@ export const useResponseColumns = () => { sortable: false, Cell: PackQuantityCell, accessor: ({ rowData }) => { - const stockOnHand = linkedRequisition - ? (rowData.linkedRequisitionLine?.itemStats.availableStockOnHand ?? 0) - : rowData.availableStockOnHand; + const stockOnHand = rowData.initialStockOnHandUnits; const incomingStock = rowData.incomingUnits + rowData.additionInUnits; const outgoingStock = rowData.lossInUnits + rowData.outgoingUnits; const available = stockOnHand + incomingStock - outgoingStock; - const averageMonthlyConsumption = linkedRequisition - ? (rowData.linkedRequisitionLine?.itemStats - .averageMonthlyConsumption ?? 0) - : rowData.averageMonthlyConsumption; + const averageMonthlyConsumption = rowData.averageMonthlyConsumption; return averageMonthlyConsumption !== 0 ? available / averageMonthlyConsumption diff --git a/client/packages/system/src/Item/Components/ColdStorageTypeInput/ColdStorageTypeInput.tsx b/client/packages/system/src/Item/Components/ColdStorageTypeInput/ColdStorageTypeInput.tsx new file mode 100644 index 0000000000..f14b04dda6 --- /dev/null +++ b/client/packages/system/src/Item/Components/ColdStorageTypeInput/ColdStorageTypeInput.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { Autocomplete, useBufferState } from '@openmsupply-client/common'; +import { useColdStorageTypes } from '../../api/hooks/useColdStorageTypes'; +import { ColdStorageTypeFragment } from '../../api'; + +export interface ColdStorageTypeInputProps { + onChange: (name: ColdStorageTypeFragment | null) => void; + onInputChange?: ( + event: React.SyntheticEvent, + value: string, + reason: string + ) => void; + width?: number; + label?: string; + value: ColdStorageTypeFragment | null; + disabled?: boolean; + clearable?: boolean; +} + +export const ColdStorageTypeInput = ({ + onChange, + width = 250, + value, + label, + disabled = false, +}: ColdStorageTypeInputProps) => { + const { data, isLoading } = useColdStorageTypes(); + const [buffer, setBuffer] = useBufferState(value); + + return ( + { + setBuffer(name); + onChange(name); + }} + options={data?.coldStorageTypes.nodes ?? []} + getOptionLabel={option => option.name} + width={`${width}px`} + popperMinWidth={width} + isOptionEqualToValue={(option, value) => option?.id === value?.id} + inputProps={{ label }} + /> + ); +}; diff --git a/client/packages/system/src/Item/Components/ColdStorageTypeInput/index.ts b/client/packages/system/src/Item/Components/ColdStorageTypeInput/index.ts new file mode 100644 index 0000000000..c103e2b169 --- /dev/null +++ b/client/packages/system/src/Item/Components/ColdStorageTypeInput/index.ts @@ -0,0 +1 @@ +export * from './ColdStorageTypeInput'; diff --git a/client/packages/system/src/Item/Components/ItemVariantSearchInput/ItemVariantInputCell.tsx b/client/packages/system/src/Item/Components/ItemVariantSearchInput/ItemVariantInputCell.tsx new file mode 100644 index 0000000000..3b8609f9d2 --- /dev/null +++ b/client/packages/system/src/Item/Components/ItemVariantSearchInput/ItemVariantInputCell.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { RecordWithId, CellProps } from '@openmsupply-client/common'; +import { ItemVariantSearchInput } from '.'; + +export const ItemVariantInputCell = ({ + rowData, + column, + isDisabled, + itemId, +}: CellProps & { itemId: string }) => { + const selectedId = column.accessor({ + rowData, + }) as string | null; + + const onChange = (itemVariantId: string | null) => { + column.setter({ ...rowData, itemVariantId }); + }; + + return ( + + ); +}; diff --git a/client/packages/system/src/Item/Components/ItemVariantSearchInput/ItemVariantSearchInput.tsx b/client/packages/system/src/Item/Components/ItemVariantSearchInput/ItemVariantSearchInput.tsx new file mode 100644 index 0000000000..3a925d5ea7 --- /dev/null +++ b/client/packages/system/src/Item/Components/ItemVariantSearchInput/ItemVariantSearchInput.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { Autocomplete, useTranslation } from '@openmsupply-client/common'; +import { useItemVariants } from '../../api'; + +interface ItemVariantSearchInputProps { + itemId: string; + selectedId: string | null; + onChange: (variantId: string | null) => void; + disabled?: boolean; + width?: number | string; +} + +export const ItemVariantSearchInput = ({ + selectedId, + width, + onChange, + disabled, + itemId, +}: ItemVariantSearchInputProps) => { + const t = useTranslation(); + const { data, isLoading } = useItemVariants(itemId); + + if (!data) return null; + + const selected = data.find(variant => variant.id === selectedId); + + return ( + onChange(option?.id ?? null)} + options={data} + noOptionsText={t('messages.no-item-variants')} + isOptionEqualToValue={(option, value) => option.id === value?.id} + clearable + /> + ); +}; diff --git a/client/packages/system/src/Item/Components/ItemVariantSearchInput/index.ts b/client/packages/system/src/Item/Components/ItemVariantSearchInput/index.ts new file mode 100644 index 0000000000..ab2ee8d2e6 --- /dev/null +++ b/client/packages/system/src/Item/Components/ItemVariantSearchInput/index.ts @@ -0,0 +1,2 @@ +export * from './ItemVariantSearchInput'; +export * from './ItemVariantInputCell'; diff --git a/client/packages/system/src/Item/Components/index.ts b/client/packages/system/src/Item/Components/index.ts index 54b4fcd61a..c991bb4ac7 100644 --- a/client/packages/system/src/Item/Components/index.ts +++ b/client/packages/system/src/Item/Components/index.ts @@ -3,3 +3,4 @@ export * from './ServiceItemSearchInput'; export * from './StockItemSearchInputWithStats'; export * from './StockItemSelectModal'; export * from './StockItemSearchInput'; +export * from './ItemVariantSearchInput'; diff --git a/client/packages/system/src/Item/DetailView/Tabs/ItemVariants/ItemVariantModal.tsx b/client/packages/system/src/Item/DetailView/Tabs/ItemVariants/ItemVariantModal.tsx index 6e3b46266d..29bbc2e929 100644 --- a/client/packages/system/src/Item/DetailView/Tabs/ItemVariants/ItemVariantModal.tsx +++ b/client/packages/system/src/Item/DetailView/Tabs/ItemVariants/ItemVariantModal.tsx @@ -21,6 +21,7 @@ import { useItemVariant, } from '../../../api'; import { ManufacturerSearchInput } from '@openmsupply-client/system'; +import { ColdStorageTypeInput } from '../../../Components/ColdStorageTypeInput'; export const ItemVariantModal = ({ itemId, @@ -106,16 +107,17 @@ const ItemVariantForm = ({ label={t('label.temperature')} labelWidth="200" Input={ - // TODO: temp range dropdown - { - updateVariant({ - coldStorageTypeId: event.target.value, - }); - }} - fullWidth - /> + + + updateVariant({ + coldStorageType, + coldStorageTypeId: coldStorageType?.id ?? '', + }) + } + /> + } /> ) : ( itemVariants.map(v => ( - + )) )} @@ -61,12 +66,15 @@ export const ItemVariantsTab = ({ const ItemVariant = ({ variant, + itemId, onOpen, }: { + itemId: string; variant: ItemVariantFragment; onOpen: (variant?: ItemVariantFragment) => void; }) => { const t = useTranslation(); + const confirmAndDelete = useDeleteItemVariant({ itemId }); return ( @@ -83,7 +91,7 @@ const ItemVariant = ({ /> {}} + onClick={() => confirmAndDelete(variant.id)} startIcon={} color="primary" /> @@ -102,9 +110,8 @@ const ItemVariant = ({ label={t('label.temperature')} labelWidth="200" Input={ - // TODO: temp range dropdown diff --git a/client/packages/system/src/Item/api/hooks/useColdStorageTypes/index.ts b/client/packages/system/src/Item/api/hooks/useColdStorageTypes/index.ts new file mode 100644 index 0000000000..296b13f142 --- /dev/null +++ b/client/packages/system/src/Item/api/hooks/useColdStorageTypes/index.ts @@ -0,0 +1 @@ +export * from './useColdStorageTypes'; diff --git a/client/packages/system/src/Item/api/hooks/useColdStorageTypes/useColdStorageTypes.ts b/client/packages/system/src/Item/api/hooks/useColdStorageTypes/useColdStorageTypes.ts new file mode 100644 index 0000000000..8b121f6459 --- /dev/null +++ b/client/packages/system/src/Item/api/hooks/useColdStorageTypes/useColdStorageTypes.ts @@ -0,0 +1,10 @@ +import { useQuery } from '@openmsupply-client/common'; +import { useItemGraphQL } from '../useItemApi'; + +export const useColdStorageTypes = () => { + const { api, storeId } = useItemGraphQL(); + + const coldStorageTypesKey = 'coldStorageTypes'; + + return useQuery(coldStorageTypesKey, () => api.coldStorageTypes({ storeId })); +}; diff --git a/client/packages/system/src/Item/api/hooks/useItemVariant/index.ts b/client/packages/system/src/Item/api/hooks/useItemVariant/index.ts index 22c411435d..89f09cb27d 100644 --- a/client/packages/system/src/Item/api/hooks/useItemVariant/index.ts +++ b/client/packages/system/src/Item/api/hooks/useItemVariant/index.ts @@ -1 +1,3 @@ export * from './useItemVariant'; +export * from './useItemVariants'; +export * from './useDeleteItemVariant'; diff --git a/client/packages/system/src/Item/api/hooks/useItemVariant/useDeleteItemVariant.ts b/client/packages/system/src/Item/api/hooks/useItemVariant/useDeleteItemVariant.ts new file mode 100644 index 0000000000..a884d98751 --- /dev/null +++ b/client/packages/system/src/Item/api/hooks/useItemVariant/useDeleteItemVariant.ts @@ -0,0 +1,51 @@ +import { + isEmpty, + useMutation, + useTranslation, + useNotification, + useConfirmationModal, +} from '@openmsupply-client/common'; +import { useItemApi, useItemGraphQL } from '../useItemApi'; + +export const useDeleteItemVariant = ({ itemId }: { itemId: string }) => { + const { api, storeId, queryClient } = useItemGraphQL(); + const { keys } = useItemApi(); + const t = useTranslation(); + const { success } = useNotification(); + + const mutationFn = async (id: string) => { + const apiResult = await api.deleteItemVariant({ + storeId, + input: { id }, + }); + // will be empty if there's a generic error, such as permission denied + if (!isEmpty(apiResult)) { + const result = apiResult.centralServer.itemVariant.deleteItemVariant; + if (result.__typename === 'DeleteResponse') { + return result; + } + } + throw new Error(t('error.failed-to-delete-item-variant')); + }; + + const { mutateAsync } = useMutation({ + mutationFn, + onSuccess: () => { + queryClient.invalidateQueries(keys.detail(itemId)); + }, + }); + + const showDeleteConfirmation = useConfirmationModal({ + title: t('heading.are-you-sure'), + message: t('messages.confirm-delete-item-variant'), + }); + + return (id: string) => { + showDeleteConfirmation({ + onConfirm: async () => { + await mutateAsync(id); + success(t('messages.deleted-item-variant'))(); + }, + }); + }; +}; diff --git a/client/packages/system/src/Item/api/hooks/useItemVariant/useItemVariants.ts b/client/packages/system/src/Item/api/hooks/useItemVariant/useItemVariants.ts new file mode 100644 index 0000000000..c1c4be5693 --- /dev/null +++ b/client/packages/system/src/Item/api/hooks/useItemVariant/useItemVariants.ts @@ -0,0 +1,19 @@ +import { useQuery } from '@openmsupply-client/common'; +import { useItemGraphQL } from '..'; +import { ITEM_VARIANTS } from '../../keys'; + +export function useItemVariants(itemId: string) { + const { api, storeId } = useItemGraphQL(); + + return useQuery({ + queryKey: [ITEM_VARIANTS, itemId], + queryFn: async () => { + const result = await api.itemVariants({ + itemId, + storeId, + }); + + return result.items.nodes?.[0]?.variants; + }, + }); +} diff --git a/client/packages/system/src/Item/api/keys.ts b/client/packages/system/src/Item/api/keys.ts new file mode 100644 index 0000000000..f0d1272121 --- /dev/null +++ b/client/packages/system/src/Item/api/keys.ts @@ -0,0 +1 @@ +export const ITEM_VARIANTS = 'ITEM_VARIANTS'; diff --git a/client/packages/system/src/Item/api/operations.generated.ts b/client/packages/system/src/Item/api/operations.generated.ts index b2db217f92..7b531b9ff5 100644 --- a/client/packages/system/src/Item/api/operations.generated.ts +++ b/client/packages/system/src/Item/api/operations.generated.ts @@ -16,11 +16,13 @@ export type ItemStockOnHandFragment = { __typename: 'ItemNode', availableStockOn export type ItemRowWithStatsFragment = { __typename: 'ItemNode', availableStockOnHand: number, defaultPackSize: number, id: string, code: string, name: string, unitName?: string | null, stats: { __typename: 'ItemStatsNode', averageMonthlyConsumption: number, availableStockOnHand: number, availableMonthsOfStockOnHand?: number | null, totalConsumption: number } }; +export type ColdStorageTypeFragment = { __typename: 'ColdStorageTypeNode', id: string, name: string, minTemperature: number, maxTemperature: number }; + export type PackagingVariantFragment = { __typename: 'PackagingVariantNode', id: string, name: string, packagingLevel: number, packSize?: number | null, volumePerUnit?: number | null }; -export type ItemVariantFragment = { __typename: 'ItemVariantNode', id: string, name: string, dosesPerUnit?: number | null, manufacturerId?: string | null, coldStorageTypeId?: string | null, manufacturer?: { __typename: 'NameNode', code: string, id: string, isCustomer: boolean, isSupplier: boolean, isOnHold: boolean, name: string, store?: { __typename: 'StoreNode', id: string, code: string } | null } | null, packagingVariants: Array<{ __typename: 'PackagingVariantNode', id: string, name: string, packagingLevel: number, packSize?: number | null, volumePerUnit?: number | null }> }; +export type ItemVariantFragment = { __typename: 'ItemVariantNode', id: string, name: string, dosesPerUnit?: number | null, manufacturerId?: string | null, coldStorageTypeId?: string | null, manufacturer?: { __typename: 'NameNode', code: string, id: string, isCustomer: boolean, isSupplier: boolean, isOnHold: boolean, name: string, store?: { __typename: 'StoreNode', id: string, code: string } | null } | null, coldStorageType?: { __typename: 'ColdStorageTypeNode', id: string, name: string, minTemperature: number, maxTemperature: number } | null, packagingVariants: Array<{ __typename: 'PackagingVariantNode', id: string, name: string, packagingLevel: number, packSize?: number | null, volumePerUnit?: number | null }> }; -export type ItemFragment = { __typename: 'ItemNode', id: string, code: string, name: string, atcCategory: string, ddd: string, defaultPackSize: number, doses: number, isVaccine: boolean, margin: number, msupplyUniversalCode: string, msupplyUniversalName: string, outerPackSize: number, strength?: string | null, type: Types.ItemNodeType, unitName?: string | null, volumePerOuterPack: number, volumePerPack: number, weight: number, availableStockOnHand: number, availableBatches: { __typename: 'StockLineConnector', totalCount: number, nodes: Array<{ __typename: 'StockLineNode', availableNumberOfPacks: number, batch?: string | null, costPricePerPack: number, expiryDate?: string | null, id: string, itemId: string, note?: string | null, onHold: boolean, packSize: number, sellPricePerPack: number, storeId: string, totalNumberOfPacks: number, location?: { __typename: 'LocationNode', code: string, id: string, name: string, onHold: boolean } | null, item: { __typename: 'ItemNode', name: string, code: string, unitName?: string | null, doses: number } }> }, stats: { __typename: 'ItemStatsNode', averageMonthlyConsumption: number, availableStockOnHand: number, availableMonthsOfStockOnHand?: number | null, totalConsumption: number }, variants: Array<{ __typename: 'ItemVariantNode', id: string, name: string, dosesPerUnit?: number | null, manufacturerId?: string | null, coldStorageTypeId?: string | null, manufacturer?: { __typename: 'NameNode', code: string, id: string, isCustomer: boolean, isSupplier: boolean, isOnHold: boolean, name: string, store?: { __typename: 'StoreNode', id: string, code: string } | null } | null, packagingVariants: Array<{ __typename: 'PackagingVariantNode', id: string, name: string, packagingLevel: number, packSize?: number | null, volumePerUnit?: number | null }> }> }; +export type ItemFragment = { __typename: 'ItemNode', id: string, code: string, name: string, atcCategory: string, ddd: string, defaultPackSize: number, doses: number, isVaccine: boolean, margin: number, msupplyUniversalCode: string, msupplyUniversalName: string, outerPackSize: number, strength?: string | null, type: Types.ItemNodeType, unitName?: string | null, volumePerOuterPack: number, volumePerPack: number, weight: number, availableStockOnHand: number, availableBatches: { __typename: 'StockLineConnector', totalCount: number, nodes: Array<{ __typename: 'StockLineNode', availableNumberOfPacks: number, batch?: string | null, costPricePerPack: number, expiryDate?: string | null, id: string, itemId: string, note?: string | null, onHold: boolean, packSize: number, sellPricePerPack: number, storeId: string, totalNumberOfPacks: number, location?: { __typename: 'LocationNode', code: string, id: string, name: string, onHold: boolean } | null, item: { __typename: 'ItemNode', name: string, code: string, unitName?: string | null, doses: number } }> }, stats: { __typename: 'ItemStatsNode', averageMonthlyConsumption: number, availableStockOnHand: number, availableMonthsOfStockOnHand?: number | null, totalConsumption: number }, variants: Array<{ __typename: 'ItemVariantNode', id: string, name: string, dosesPerUnit?: number | null, manufacturerId?: string | null, coldStorageTypeId?: string | null, manufacturer?: { __typename: 'NameNode', code: string, id: string, isCustomer: boolean, isSupplier: boolean, isOnHold: boolean, name: string, store?: { __typename: 'StoreNode', id: string, code: string } | null } | null, coldStorageType?: { __typename: 'ColdStorageTypeNode', id: string, name: string, minTemperature: number, maxTemperature: number } | null, packagingVariants: Array<{ __typename: 'PackagingVariantNode', id: string, name: string, packagingLevel: number, packSize?: number | null, volumePerUnit?: number | null }> }> }; export type ItemsWithStockLinesQueryVariables = Types.Exact<{ first?: Types.InputMaybe; @@ -32,7 +34,7 @@ export type ItemsWithStockLinesQueryVariables = Types.Exact<{ }>; -export type ItemsWithStockLinesQuery = { __typename: 'Queries', items: { __typename: 'ItemConnector', totalCount: number, nodes: Array<{ __typename: 'ItemNode', id: string, code: string, name: string, atcCategory: string, ddd: string, defaultPackSize: number, doses: number, isVaccine: boolean, margin: number, msupplyUniversalCode: string, msupplyUniversalName: string, outerPackSize: number, strength?: string | null, type: Types.ItemNodeType, unitName?: string | null, volumePerOuterPack: number, volumePerPack: number, weight: number, availableStockOnHand: number, availableBatches: { __typename: 'StockLineConnector', totalCount: number, nodes: Array<{ __typename: 'StockLineNode', availableNumberOfPacks: number, batch?: string | null, costPricePerPack: number, expiryDate?: string | null, id: string, itemId: string, note?: string | null, onHold: boolean, packSize: number, sellPricePerPack: number, storeId: string, totalNumberOfPacks: number, location?: { __typename: 'LocationNode', code: string, id: string, name: string, onHold: boolean } | null, item: { __typename: 'ItemNode', name: string, code: string, unitName?: string | null, doses: number } }> }, stats: { __typename: 'ItemStatsNode', averageMonthlyConsumption: number, availableStockOnHand: number, availableMonthsOfStockOnHand?: number | null, totalConsumption: number }, variants: Array<{ __typename: 'ItemVariantNode', id: string, name: string, dosesPerUnit?: number | null, manufacturerId?: string | null, coldStorageTypeId?: string | null, manufacturer?: { __typename: 'NameNode', code: string, id: string, isCustomer: boolean, isSupplier: boolean, isOnHold: boolean, name: string, store?: { __typename: 'StoreNode', id: string, code: string } | null } | null, packagingVariants: Array<{ __typename: 'PackagingVariantNode', id: string, name: string, packagingLevel: number, packSize?: number | null, volumePerUnit?: number | null }> }> }> } }; +export type ItemsWithStockLinesQuery = { __typename: 'Queries', items: { __typename: 'ItemConnector', totalCount: number, nodes: Array<{ __typename: 'ItemNode', id: string, code: string, name: string, atcCategory: string, ddd: string, defaultPackSize: number, doses: number, isVaccine: boolean, margin: number, msupplyUniversalCode: string, msupplyUniversalName: string, outerPackSize: number, strength?: string | null, type: Types.ItemNodeType, unitName?: string | null, volumePerOuterPack: number, volumePerPack: number, weight: number, availableStockOnHand: number, availableBatches: { __typename: 'StockLineConnector', totalCount: number, nodes: Array<{ __typename: 'StockLineNode', availableNumberOfPacks: number, batch?: string | null, costPricePerPack: number, expiryDate?: string | null, id: string, itemId: string, note?: string | null, onHold: boolean, packSize: number, sellPricePerPack: number, storeId: string, totalNumberOfPacks: number, location?: { __typename: 'LocationNode', code: string, id: string, name: string, onHold: boolean } | null, item: { __typename: 'ItemNode', name: string, code: string, unitName?: string | null, doses: number } }> }, stats: { __typename: 'ItemStatsNode', averageMonthlyConsumption: number, availableStockOnHand: number, availableMonthsOfStockOnHand?: number | null, totalConsumption: number }, variants: Array<{ __typename: 'ItemVariantNode', id: string, name: string, dosesPerUnit?: number | null, manufacturerId?: string | null, coldStorageTypeId?: string | null, manufacturer?: { __typename: 'NameNode', code: string, id: string, isCustomer: boolean, isSupplier: boolean, isOnHold: boolean, name: string, store?: { __typename: 'StoreNode', id: string, code: string } | null } | null, coldStorageType?: { __typename: 'ColdStorageTypeNode', id: string, name: string, minTemperature: number, maxTemperature: number } | null, packagingVariants: Array<{ __typename: 'PackagingVariantNode', id: string, name: string, packagingLevel: number, packSize?: number | null, volumePerUnit?: number | null }> }> }> } }; export type ItemsQueryVariables = Types.Exact<{ first?: Types.InputMaybe; @@ -78,7 +80,17 @@ export type ItemByIdQueryVariables = Types.Exact<{ }>; -export type ItemByIdQuery = { __typename: 'Queries', items: { __typename: 'ItemConnector', totalCount: number, nodes: Array<{ __typename: 'ItemNode', id: string, code: string, name: string, atcCategory: string, ddd: string, defaultPackSize: number, doses: number, isVaccine: boolean, margin: number, msupplyUniversalCode: string, msupplyUniversalName: string, outerPackSize: number, strength?: string | null, type: Types.ItemNodeType, unitName?: string | null, volumePerOuterPack: number, volumePerPack: number, weight: number, availableStockOnHand: number, stats: { __typename: 'ItemStatsNode', averageMonthlyConsumption: number, availableStockOnHand: number, availableMonthsOfStockOnHand?: number | null, totalConsumption: number }, availableBatches: { __typename: 'StockLineConnector', totalCount: number, nodes: Array<{ __typename: 'StockLineNode', availableNumberOfPacks: number, batch?: string | null, costPricePerPack: number, expiryDate?: string | null, id: string, itemId: string, note?: string | null, onHold: boolean, packSize: number, sellPricePerPack: number, storeId: string, totalNumberOfPacks: number, location?: { __typename: 'LocationNode', code: string, id: string, name: string, onHold: boolean } | null, item: { __typename: 'ItemNode', name: string, code: string, unitName?: string | null, doses: number } }> }, variants: Array<{ __typename: 'ItemVariantNode', id: string, name: string, dosesPerUnit?: number | null, manufacturerId?: string | null, coldStorageTypeId?: string | null, manufacturer?: { __typename: 'NameNode', code: string, id: string, isCustomer: boolean, isSupplier: boolean, isOnHold: boolean, name: string, store?: { __typename: 'StoreNode', id: string, code: string } | null } | null, packagingVariants: Array<{ __typename: 'PackagingVariantNode', id: string, name: string, packagingLevel: number, packSize?: number | null, volumePerUnit?: number | null }> }> }> } }; +export type ItemByIdQuery = { __typename: 'Queries', items: { __typename: 'ItemConnector', totalCount: number, nodes: Array<{ __typename: 'ItemNode', id: string, code: string, name: string, atcCategory: string, ddd: string, defaultPackSize: number, doses: number, isVaccine: boolean, margin: number, msupplyUniversalCode: string, msupplyUniversalName: string, outerPackSize: number, strength?: string | null, type: Types.ItemNodeType, unitName?: string | null, volumePerOuterPack: number, volumePerPack: number, weight: number, availableStockOnHand: number, stats: { __typename: 'ItemStatsNode', averageMonthlyConsumption: number, availableStockOnHand: number, availableMonthsOfStockOnHand?: number | null, totalConsumption: number }, availableBatches: { __typename: 'StockLineConnector', totalCount: number, nodes: Array<{ __typename: 'StockLineNode', availableNumberOfPacks: number, batch?: string | null, costPricePerPack: number, expiryDate?: string | null, id: string, itemId: string, note?: string | null, onHold: boolean, packSize: number, sellPricePerPack: number, storeId: string, totalNumberOfPacks: number, location?: { __typename: 'LocationNode', code: string, id: string, name: string, onHold: boolean } | null, item: { __typename: 'ItemNode', name: string, code: string, unitName?: string | null, doses: number } }> }, variants: Array<{ __typename: 'ItemVariantNode', id: string, name: string, dosesPerUnit?: number | null, manufacturerId?: string | null, coldStorageTypeId?: string | null, manufacturer?: { __typename: 'NameNode', code: string, id: string, isCustomer: boolean, isSupplier: boolean, isOnHold: boolean, name: string, store?: { __typename: 'StoreNode', id: string, code: string } | null } | null, coldStorageType?: { __typename: 'ColdStorageTypeNode', id: string, name: string, minTemperature: number, maxTemperature: number } | null, packagingVariants: Array<{ __typename: 'PackagingVariantNode', id: string, name: string, packagingLevel: number, packSize?: number | null, volumePerUnit?: number | null }> }> }> } }; + +export type ItemVariantOptionFragment = { __typename: 'ItemVariantNode', id: string, label: string }; + +export type ItemVariantsQueryVariables = Types.Exact<{ + storeId: Types.Scalars['String']['input']; + itemId: Types.Scalars['String']['input']; +}>; + + +export type ItemVariantsQuery = { __typename: 'Queries', items: { __typename: 'ItemConnector', nodes: Array<{ __typename: 'ItemNode', variants: Array<{ __typename: 'ItemVariantNode', id: string, label: string }> }> } }; export type GetHistoricalStockLinesQueryVariables = Types.Exact<{ storeId: Types.Scalars['String']['input']; @@ -95,7 +107,22 @@ export type UpsertItemVariantMutationVariables = Types.Exact<{ }>; -export type UpsertItemVariantMutation = { __typename: 'Mutations', centralServer: { __typename: 'CentralServerMutationNode', itemVariant: { __typename: 'ItemVariantMutations', upsertItemVariant: { __typename: 'ItemVariantNode', id: string, name: string, dosesPerUnit?: number | null, manufacturerId?: string | null, coldStorageTypeId?: string | null, manufacturer?: { __typename: 'NameNode', code: string, id: string, isCustomer: boolean, isSupplier: boolean, isOnHold: boolean, name: string, store?: { __typename: 'StoreNode', id: string, code: string } | null } | null, packagingVariants: Array<{ __typename: 'PackagingVariantNode', id: string, name: string, packagingLevel: number, packSize?: number | null, volumePerUnit?: number | null }> } | { __typename: 'UpsertItemVariantError' } } } }; +export type UpsertItemVariantMutation = { __typename: 'Mutations', centralServer: { __typename: 'CentralServerMutationNode', itemVariant: { __typename: 'ItemVariantMutations', upsertItemVariant: { __typename: 'ItemVariantNode', id: string, name: string, dosesPerUnit?: number | null, manufacturerId?: string | null, coldStorageTypeId?: string | null, manufacturer?: { __typename: 'NameNode', code: string, id: string, isCustomer: boolean, isSupplier: boolean, isOnHold: boolean, name: string, store?: { __typename: 'StoreNode', id: string, code: string } | null } | null, coldStorageType?: { __typename: 'ColdStorageTypeNode', id: string, name: string, minTemperature: number, maxTemperature: number } | null, packagingVariants: Array<{ __typename: 'PackagingVariantNode', id: string, name: string, packagingLevel: number, packSize?: number | null, volumePerUnit?: number | null }> } | { __typename: 'UpsertItemVariantError' } } } }; + +export type DeleteItemVariantMutationVariables = Types.Exact<{ + storeId: Types.Scalars['String']['input']; + input: Types.DeleteItemVariantInput; +}>; + + +export type DeleteItemVariantMutation = { __typename: 'Mutations', centralServer: { __typename: 'CentralServerMutationNode', itemVariant: { __typename: 'ItemVariantMutations', deleteItemVariant: { __typename: 'DeleteResponse', id: string } } } }; + +export type ColdStorageTypesQueryVariables = Types.Exact<{ + storeId: Types.Scalars['String']['input']; +}>; + + +export type ColdStorageTypesQuery = { __typename: 'Queries', coldStorageTypes: { __typename: 'ColdStorageTypeConnector', nodes: Array<{ __typename: 'ColdStorageTypeNode', id: string, name: string, minTemperature: number, maxTemperature: number }> } }; export const ServiceItemRowFragmentDoc = gql` fragment ServiceItemRow on ItemNode { @@ -167,6 +194,15 @@ export const StockLineFragmentDoc = gql` totalNumberOfPacks } `; +export const ColdStorageTypeFragmentDoc = gql` + fragment ColdStorageType on ColdStorageTypeNode { + __typename + id + name + minTemperature + maxTemperature +} + `; export const PackagingVariantFragmentDoc = gql` fragment PackagingVariant on PackagingVariantNode { __typename @@ -188,11 +224,15 @@ export const ItemVariantFragmentDoc = gql` ...NameRow } coldStorageTypeId + coldStorageType { + ...ColdStorageType + } packagingVariants { ...PackagingVariant } } ${NameRowFragmentDoc} +${ColdStorageTypeFragmentDoc} ${PackagingVariantFragmentDoc}`; export const ItemFragmentDoc = gql` fragment Item on ItemNode { @@ -255,6 +295,13 @@ export const ItemsWithStatsFragmentDoc = gql` } } `; +export const ItemVariantOptionFragmentDoc = gql` + fragment ItemVariantOption on ItemVariantNode { + __typename + id + label: name +} + `; export const ItemsWithStockLinesDocument = gql` query itemsWithStockLines($first: Int, $offset: Int, $key: ItemSortFieldInput!, $desc: Boolean, $filter: ItemFilterInput, $storeId: String!) { items( @@ -354,6 +401,21 @@ export const ItemByIdDocument = gql` } ${ItemFragmentDoc} ${StockLineFragmentDoc}`; +export const ItemVariantsDocument = gql` + query itemVariants($storeId: String!, $itemId: String!) { + items(storeId: $storeId, filter: {id: {equalTo: $itemId}, isActive: true}) { + ... on ItemConnector { + __typename + nodes { + __typename + variants { + ...ItemVariantOption + } + } + } + } +} + ${ItemVariantOptionFragmentDoc}`; export const GetHistoricalStockLinesDocument = gql` query getHistoricalStockLines($storeId: String!, $itemId: String!, $datetime: DateTime) { historicalStockLines(storeId: $storeId, itemId: $itemId, datetime: $datetime) { @@ -379,6 +441,32 @@ export const UpsertItemVariantDocument = gql` } } ${ItemVariantFragmentDoc}`; +export const DeleteItemVariantDocument = gql` + mutation deleteItemVariant($storeId: String!, $input: DeleteItemVariantInput!) { + centralServer { + itemVariant { + deleteItemVariant(storeId: $storeId, input: $input) { + __typename + ... on DeleteResponse { + __typename + id + } + } + } + } +} + `; +export const ColdStorageTypesDocument = gql` + query coldStorageTypes($storeId: String!) { + coldStorageTypes(storeId: $storeId) { + ... on ColdStorageTypeConnector { + nodes { + ...ColdStorageType + } + } + } +} + ${ColdStorageTypeFragmentDoc}`; export type SdkFunctionWrapper = (action: (requestHeaders?:Record) => Promise, operationName: string, operationType?: string, variables?: any) => Promise; @@ -402,11 +490,20 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = itemById(variables: ItemByIdQueryVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { return withWrapper((wrappedRequestHeaders) => client.request(ItemByIdDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'itemById', 'query', variables); }, + itemVariants(variables: ItemVariantsQueryVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(ItemVariantsDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'itemVariants', 'query', variables); + }, getHistoricalStockLines(variables: GetHistoricalStockLinesQueryVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { return withWrapper((wrappedRequestHeaders) => client.request(GetHistoricalStockLinesDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'getHistoricalStockLines', 'query', variables); }, upsertItemVariant(variables: UpsertItemVariantMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { return withWrapper((wrappedRequestHeaders) => client.request(UpsertItemVariantDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'upsertItemVariant', 'mutation', variables); + }, + deleteItemVariant(variables: DeleteItemVariantMutationVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(DeleteItemVariantDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'deleteItemVariant', 'mutation', variables); + }, + coldStorageTypes(variables: ColdStorageTypesQueryVariables, requestHeaders?: GraphQLClientRequestHeaders): Promise { + return withWrapper((wrappedRequestHeaders) => client.request(ColdStorageTypesDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'coldStorageTypes', 'query', variables); } }; } diff --git a/client/packages/system/src/Item/api/operations.graphql b/client/packages/system/src/Item/api/operations.graphql index b5ad865ea3..eb2198d8c0 100644 --- a/client/packages/system/src/Item/api/operations.graphql +++ b/client/packages/system/src/Item/api/operations.graphql @@ -60,6 +60,14 @@ fragment ItemRowWithStats on ItemNode { } } +fragment ColdStorageType on ColdStorageTypeNode { + __typename + id + name + minTemperature + maxTemperature +} + fragment PackagingVariant on PackagingVariantNode { __typename id @@ -79,6 +87,9 @@ fragment ItemVariant on ItemVariantNode { ...NameRow } coldStorageTypeId + coldStorageType { + ...ColdStorageType + } packagingVariants { ...PackagingVariant } @@ -266,6 +277,29 @@ query itemById($storeId: String!, $itemId: String!) { } } +fragment ItemVariantOption on ItemVariantNode { + __typename + id + label: name +} + +query itemVariants($storeId: String!, $itemId: String!) { + items( + storeId: $storeId + filter: { id: { equalTo: $itemId }, isActive: true } + ) { + ... on ItemConnector { + __typename + nodes { + __typename + variants { + ...ItemVariantOption + } + } + } + } +} + query getHistoricalStockLines( $storeId: String! $itemId: String! @@ -296,3 +330,27 @@ mutation upsertItemVariant($storeId: String!, $input: UpsertItemVariantInput!) { } } } + +mutation deleteItemVariant($storeId: String!, $input: DeleteItemVariantInput!) { + centralServer { + itemVariant { + deleteItemVariant(storeId: $storeId, input: $input) { + __typename + ... on DeleteResponse { + __typename + id + } + } + } + } +} + +query coldStorageTypes($storeId: String!) { + coldStorageTypes(storeId: $storeId) { + ... on ColdStorageTypeConnector { + nodes { + ...ColdStorageType + } + } + } +} diff --git a/client/packages/system/src/Location/ListView/LocationEditModal/LocationEditModal.tsx b/client/packages/system/src/Location/ListView/LocationEditModal/LocationEditModal.tsx index f628007c82..5e46e970a9 100644 --- a/client/packages/system/src/Location/ListView/LocationEditModal/LocationEditModal.tsx +++ b/client/packages/system/src/Location/ListView/LocationEditModal/LocationEditModal.tsx @@ -11,6 +11,7 @@ import { InlineSpinner, } from '@openmsupply-client/common'; import { LocationRowFragment, useLocation } from '../../api'; +import { ColdStorageTypeInput } from 'packages/system/src/Item/Components/ColdStorageTypeInput'; interface LocationEditModalProps { mode: ModalMode | null; isOpen: boolean; @@ -139,6 +140,11 @@ export const LocationEditModal: FC = ({ label={t('label.code')} InputLabelProps={{ shrink: true }} /> + onUpdate({ coldStorageType })} + /> ({ - id: location?.id, - name: location?.name, - code: location?.code, - onHold: location?.onHold, - }), + toUpdate: (location: LocationRowFragment): UpdateLocationInput => { + return { + id: location?.id, + name: location?.name, + code: location?.code, + onHold: location?.onHold, + coldStorageTypeId: location?.coldStorageType?.id ?? null, + }; + }, }; export const getLocationQueries = (sdk: Sdk, storeId: string) => ({ diff --git a/client/packages/system/src/Name/Components/ManufacturerSearchInput/ManufacturerSearchInput.tsx b/client/packages/system/src/Name/Components/ManufacturerSearchInput/ManufacturerSearchInput.tsx index 8f3fa7f03b..d7a1b7fa9a 100644 --- a/client/packages/system/src/Name/Components/ManufacturerSearchInput/ManufacturerSearchInput.tsx +++ b/client/packages/system/src/Name/Components/ManufacturerSearchInput/ManufacturerSearchInput.tsx @@ -18,8 +18,7 @@ export const ManufacturerSearchInput = ({ value, disabled = false, }: NameSearchInputProps) => { - // TODO: currently using supplier list, should be manufacturers! - const { data, isLoading } = useName.document.suppliers(); + const { data, isLoading } = useName.document.manufacturers(); const [buffer, setBuffer] = useBufferState(value); const t = useTranslation(); const NameOptionRenderer = getNameOptionRenderer(t('label.on-hold')); diff --git a/client/packages/system/src/Name/api/api.ts b/client/packages/system/src/Name/api/api.ts index 2ed1d69969..41daa3184a 100644 --- a/client/packages/system/src/Name/api/api.ts +++ b/client/packages/system/src/Name/api/api.ts @@ -80,6 +80,21 @@ export const getNameQueries = (sdk: Sdk, storeId: string) => ({ return result?.names; }, + manufacturers: async ({ sortBy }: ListParams) => { + const key = nameParsers.toSort(sortBy?.key ?? ''); + + const result = await sdk.names({ + key, + desc: !!sortBy?.isDesc, + storeId, + filter: { + isManufacturer: true, + }, + first: 1000, + }); + + return result?.names; + }, customers: async ({ sortBy }: ListParams) => { const key = nameParsers.toSort(sortBy?.key ?? ''); diff --git a/client/packages/system/src/Name/api/hooks/document/index.ts b/client/packages/system/src/Name/api/hooks/document/index.ts index c0070db638..e88bfbabca 100644 --- a/client/packages/system/src/Name/api/hooks/document/index.ts +++ b/client/packages/system/src/Name/api/hooks/document/index.ts @@ -1,5 +1,6 @@ import { useCustomers } from './useCustomers'; import { useSuppliers } from './useSuppliers'; +import { useManufacturers } from './useManufacturers'; import { useInternalSuppliers } from './useInternalSuppliers'; import { useName } from './useName'; import { useNames } from './useNames'; @@ -13,6 +14,7 @@ export const Document = { useCustomers, useFacilities, useSuppliers, + useManufacturers, useInternalSuppliers, useName, useUpdateProperties, diff --git a/client/packages/system/src/Name/api/hooks/document/useManufacturers.ts b/client/packages/system/src/Name/api/hooks/document/useManufacturers.ts new file mode 100644 index 0000000000..5887d7092f --- /dev/null +++ b/client/packages/system/src/Name/api/hooks/document/useManufacturers.ts @@ -0,0 +1,11 @@ +import { useQuery, useQueryParamsStore } from '@openmsupply-client/common'; +import { useNameApi } from '../utils/useNameApi'; + +export const useManufacturers = () => { + const api = useNameApi(); + const queryParams = useQueryParamsStore(); + + return useQuery(api.keys.paramList(queryParams.paramList()), () => + api.get.manufacturers(queryParams.paramList()) + ); +}; diff --git a/client/packages/system/src/Name/api/hooks/index.ts b/client/packages/system/src/Name/api/hooks/index.ts index e07c75e942..e0a66e14fe 100644 --- a/client/packages/system/src/Name/api/hooks/index.ts +++ b/client/packages/system/src/Name/api/hooks/index.ts @@ -10,6 +10,7 @@ export const useName = { internalSuppliers: Document.useInternalSuppliers, list: Document.useNames, suppliers: Document.useSuppliers, + manufacturers: Document.useManufacturers, facilities: Document.useFacilities, facilitiesAll: Document.useFacilitiesAll, donors: Document.useDonors, diff --git a/client/packages/system/src/Stock/Components/StockLineForm.tsx b/client/packages/system/src/Stock/Components/StockLineForm.tsx index 24d7509de9..15e8be4f53 100644 --- a/client/packages/system/src/Stock/Components/StockLineForm.tsx +++ b/client/packages/system/src/Stock/Components/StockLineForm.tsx @@ -21,6 +21,7 @@ import { } from '@openmsupply-client/common'; import { StockLineRowFragment } from '../api'; import { LocationSearchInput } from '../../Location/Components/LocationSearchInput'; +import { ItemVariantSearchInput } from '../..'; import { StyledInputRow } from './StyledInputRow'; import { PackSizeNumberInput } from '../../Item'; @@ -158,6 +159,17 @@ export const StockLineForm: FC = ({ /> } /> + onUpdate({ itemVariantId: id })} + /> + } + /> {plugins} = ({ /> - {/* {footerProps &&