Repository: msupply-foundation/open-msupply
Pull request #6933: 6848 upgrade asset labels
Author: login: zachariah-at-msupply
Labels: name: feature: GAPS
Created: 2025-03-13T22:56:58Z | Merged: 2025-03-26T22:48:12Z

<!-- IMPORTANT!
  - Every PR must reference an issue; this helps to explain the intent of the PR
 -->

Fixes #6848 and #7065

# 👩🏻‍💻 What does this PR do?

This PR upgrades the asset label that open mSupply prints by:
- changing the format of the code printed on an asset label from a QR code to a data matrix barcode.
- adding a date printed field 
- adding the mSupply logo

Before:

![label-before](https://github.com/user-attachments/assets/64a555e6-ca5c-49b2-bac5-1a8f9ea4cd21)

After:

![telegram-cloud-photo-size-1-5100522897113460621-y](https://github.com/user-attachments/assets/936301e6-b71a-4f25-a8aa-d4960979c8e1)

## 💌 Any notes for the reviewer?

1. The template literal I created on line 41 of client/packages/coldchain/src/Equipment/DetailView/AppBarButtons.tsx is very long:
    - This seems to be industry best practice for template literals so I left it on one line ([AirBnB](https://github.com/airbnb/javascript#strings--line-length) and [Google](https://google.github.io/styleguide/jsguide.html#features-strings-no-line-continuations) style guides recommend this).
    - It will also cause unwanted new lines in the output label if it is broken onto multiple lines, so we would need to do some further modification (e.g. changing the printing method) to achieve this.

2. Line 31 and 32 of server/service/src/print/label.rs define a hard-coded mSupply logo:
    - The logo is the official Open mSupply logo foudn on our cloud storage. I was unable to find a brand book or other document with our rules for use of the logo, but I think it should be fine.
    - It's easy to update it, so let me know if that would be good, and I have written a procedure for this, but need clarification about where that should go in our docs. 
    - If it is desirable for the code to reference a file rather than a hardcoded value, my research indicates we would probably need to convert the file into ZPL using our code, which will not be straightforward, and is why I used a hardcoded value.

3. There's a comment in server/service/src/print/label.rs about how to modify the code to print a QR code instead of a data matrix barcode. This is my interpretation of what the issue was aiming at when it said to keep the ability to print a QR code - just let me know if something else would be better!

# 🧪 Testing

- [ ] In Open mSupply, go to coldchain > equipment and select an asset (or make one if there are none), and the asset's detail view should appear.
- [ ] Click 'print label' in the top right.
- [ ] If mSupply tells you that you need to set up a printer, the details as of 11th March 2025 are IP = 192.168.1.224 and port = 9100.
- [ ] Check that the label looks like the 'after' picture above and has all the bits it should have:
  - data matrix barcode
  - serial number
  - asset number
  - date printed
  - mSupply logo
- [ ] Check the information on the label matches the information that Open mSupply displays.
- [ ] Check it scans, either by using mSupply android or a 2d barcode scanner, or a barcode scanning website (although that's a bit dodgy as you need to give them permission to use your camera).

# 📃 Documentation

- [x] **Part of an epic**: documentation will be completed for the feature as a whole
- [ ] **No documentation required**: no user facing changes or a bug fix which isn't a change in behaviour
- [x] **These areas should be updated or checked**: 
  - Any reference to an asset's QR code should be changed to refer to a data matrix barcode instead.
  - Any reference to the content of assets' labels should be updated to include the new content.
  - Any images of assets' labels should be updated to be of the new label.
  - Any images of assets' QR codes should be updated to be data matrix barcodes instead (they look like this:)

![image](https://github.com/user-attachments/assets/5c74ef7a-1b3f-4d11-93c0-e10525592075)

# 📃 Reviewer Checklist

The PR Reviewer(s) should fill out this section before approving the PR

**Breaking Changes**
- [x] No Breaking Changes in the Graphql API
- [ ] Technically some Breaking Changes but not expected to impact any integrations

**Issue Review**
- [x] All requirements in original issue have been covered
- [ ] A follow up issue(s) have been created to cover additional requirements

**Tests Pass**
- [ ] Postgres
- [x] SQLite
- [x] Frontend



Linked issue #6848: Modify Open mSupply to be a tool to generate & print 2D DataMatrix labels(for CCE that don't come with a 2D DataMatrix provided by the manufacturer - simple ID only)
Currently when editing viewing a cold chain asset, there's an option to print an Asset Label to a Zebra printer
![Image](https://github.com/user-attachments/assets/9edf3cb9-e1e3-4770-84fc-4fd9410c1afa)


This currently sends a `^BQ` command to the configured Zebra Printer to print a QR code (see`pub fn print_qr_code(` in the rust code)

To change this to create a datamatrix we need to use the `^BX` command again.

https://support.zebra.com/cpws/docs/zpl/BX_Command.pdf


**Acceptance Criteria**

- [ ] Print Asset Label button prints a 2d barcode instead of a QR code
- [ ] QR code functionality is retained in the code for future use.




Diff:
diff --git a/client/packages/android/README.MD b/client/packages/android/README.MD
index 71b1d54c38c..a56d6119380 100644
--- a/client/packages/android/README.MD
+++ b/client/packages/android/README.MD
@@ -4,70 +4,78 @@ The Android app can run as standalone site with a local server, as a client conn
 On Android, the server always starts on the device.
 This makes it possible to have one mobile app for all operational modes.
 The mobile app will broadcast via dns sd and will be reachable by both Mobile and Electron clients on the local network.
+
 We are using Capacitor to make use of existing optimisations and large library of native plugins.
-The UI is not bundled within Capacitor, it is served by the server the client connect to, and discovery UI is always served by local bundled server.
+The UI is not bundled within Capacitor, it is served by the server the client connects to, but the discovery UI is always served by the bundled local server.
 
 ## Building the Rust Server Library for Android
 
 Before building the Android app, the Rust server library that allows us to run the mSupply server on the mobile device needs to be built.
+
 This requires a couple of steps.
 
-- Install Android Studio (Ladybug at time of writing 15/11/24)
-- Install NDK (26.1.10909125 at the time of writing on the 8/4/24)
+- Install Android Studio (Ladybug at time of writing 15/11/24) (Instructions [here](https://developer.android.com/studio))
+- Install NDK in Android Studio (26.1.10909125 at the time of writing on the 8/4/24) (Instructions [here](https://developer.android.com/studio/projects/install-ndk)
 
 ![omSupply Android NDK](./doc/omSupply_android_ndk.png)
 
-Currently, we support only 1 Android architecture (64bit);
-
-| rust target | android ABI |
-| aarch64-linux-android | arm64-v8a |
-| ----------------------- | ----------- |
-
 To add the required build targets to Rust run:
 
 ```bash
 rustup target add aarch64-linux-android
 ```
 
-Next we have to tell Rust where to find the Android NDK binaries required to link the server library.
-This is done by setting the `NDK_BIN` env variable to the currently installed Android NDK.
-For example, to permanently add the `NDK_BIN` env variable do:
+Currently, we support only 1 Android architecture (64bit);
+
+| rust target           | android ABI |
+| --------------------- | ----------- |
+| aarch64-linux-android | arm64-v8a   |
+
+Next we have to tell Rust where to find the Android NDK binaries required to link the server library. This is done by setting the `NDK_BIN` env variable to the currently installed Android NDK.
+
+For example, to permanently add the `NDK_BIN` env variable on a mac run:
 
 ```bash
 echo "export NDK_BIN=~/Library/Android/sdk/ndk/26.1.10909125/toolchains/llvm/prebuilt/darwin-x86_64/bin/" >> ~/.zshrc
 ```
 
-Open a new terminal and validate that `$NDK_BIN` is set correctly: `ls $NDK_BIN` (this should give a list of files starting with aarch64 and armv7a)
-Note, which linker Rust should use is defined in `server/android/.cargo/config.toml`, under [target.*].
+> If you are on linux or using a different version of the NDK for some reason, you'll need to adjust this command accordingly.
 
-### Java Installation
+Run `source ~/.zshrc` to apply the changes to the current terminal.
 
-In your terminal, run `java --version` to check if you have it installed. As at 19/12/2024 version `17.0.7-tem` is tested and works with our setup
+Validate that `$NDK_BIN` is set correctly using `ls $NDK_BIN` (this should give a list of files starting with aarch64 and armv7a)
 
-- install [SDK man](https://sdkman.io/) through your terminal by running `curl -s "https://get.sdkman.io" | bash`
-- Open a new terminal
+Note, which linker Rust uses is defined in `server/android/.cargo/config.toml`, under [target._architecture_].
 
-Install Java: Run `sdk install java 17.0.7-tem`
+### Java Installation
 
-Then set the defaults using the following commands:
+In your terminal, run `java --version` to check if you have it installed. As at 19/12/2024 version `17.0.7-tem` is tested and works with our setup
+
+We normally use [SDK man](https://sdkman.io/) to manage java versions, this can be installed using the following terminal command:
+`curl -s "https://get.sdkman.io" | bash`
 
-- `sdk use java 17.0.7-tem`
-- `sdk default java 17.0.7-tem`
+Then install Java 17.0.7-tem (Temurin) using the following terminal command:
+Install the last tested version of Java: Run `sdk install java 17.0.7-tem`
+To make this the default version of Java `sdk default java 17.0.7-tem`.
 
-Go to Android Studio settings -> Build -> Build Settings
+### Set up Android Studio
 
-- Change Gradle JDK to 17.0.7-tem
-  ![omSupply Gradle JDK](./doc/omSupply_android_gradle_jdk.png)
+Now we'll set up Android Studio so that it will use the version of Java that we want.
+First of all, open Android Studio and click Open. Open '<your Open mSupply repository>/client/packages/android'.
+Then, when you're in that project, go to Android Studio settings -> Build, Execution, Deployment -> Build Tools -> Gradle.
+You should see a dropdown to select 'Default Gradle JDK'.
+That dropdown should have an option to use a JDK called '17.0.7-temurin' - select that one.
+Here's a screenshot of what that should look like (the sidebar shows you what you should click on to get to this part of the settings):
 
-You may also see prompts in Android Studio to sync - keep an eye out for these and run the sync as needed
+![choosing OMS Android Gradle JDK](./doc/choosing_OMS_Android_Gradle_JDK.png)
 
-If you don't see the option to change Gradle JDK to `17.07.7-tem`, then open the android folder from the OMS repo in Android Studio.
+You may also see prompts in Android Studio to sync - keep an eye out for these and run the sync as needed.
 
 ## Running Android Studio
 
-You can now use `yarn build:server` from the `client/packages/android/` directory, this builds the `libremote_server_lib.so` binaries and relocate them to `app/src/main/jniLibs`.
+You can now use `yarn build:server` from the `client/packages/android/` directory, this builds the `libremote_server_lib.so` binaries and relocates them to `app/src/main/jniLibs`.
 Please see `client/packages/android/app/src/main/java/org/openmsupply/client/RemoteServer.java` for more details of how these libs are loaded.
-It's important to note that web app is bundled in the remote server binary, and web app needs to be built from `client` directory with `yarn build` command, so it's best to use `yarn android:build:server` from client directory as it does both (builds web app and then build android remote server).
+It's important to note that web app is bundled in the remote server binary, and web app needs to be built from the `client` directory with `yarn build` command, so it's best to use `yarn android:build:server` from client directory as it does both (builds web app and then build android remote server).
 
 ## Scripts
 
@@ -100,20 +108,26 @@ yarn android:build:debug
 yarn android:build:release
 ```
 
+To make the an apk that you can install on a device for testing, run `yarn android:build:debug` in the client directory. You will find the resulting apk in `client/packages/android/app/build/outputs/apk/debug`, and you can then transfer it to the tablet and install it. This can be done by uploading the apk file via the android studio device manager, then clicking the apk file from the device itself. Or using [adb](https://developer.android.com/tools/adb)
+
 ### Known script issues
 
 When building apk `properties.local` files is required (it can be empty). Also can sometime get a lock on gradle build, can run `find ~/.gradle -type f -name "*.lock" -delete` to fix it
 
 ## Debugging
 
-It's recommended to use Android Studio for debugging and running Android App, there is a lot of functionality that cannot be replicated in Visual Studio Code (although there are some extensions for Visual Studio Code, they are quite limited).
+It's recommended to use Android Studio for debugging and running the Android app, because there is a lot of functionality that cannot be replicated in Visual Studio Code (although there are some extensions for Visual Studio Code, they are quite limited).
 
-Open this directory in Android Studio, this will engage gradle sync process (initialising pre-requisites etc.), if you get an error during 'sync' process Android Studio should outline the issue with suggested fix.
+Open this directory (client/packages/android) in Android Studio - this will engage the gradle sync process (initialising pre-requisites etc.), if you get an error during 'sync' process Android Studio should outline the issue with suggested fix.
 Usually you will need to either set `ANDROID_SDK_ROOT` or add `sdk.dir` in `local.properties` file, and skd is usually located in `/Users/{user}/Library/Android/sdk/`. Pressing play (run) should restart sync process.
 
 Debugging native Java code is straight forward, breakpoint in Android Studio and press debug.
 
-Debugging web app code is done by opening `chrome://inspect`, you should see the Web View when the app is running in emulator. When bundled is served from remote server (default setup), js bundle is minimised and this makes debugging very hard (and doesn't allow for hot reload), however we can run the bundle from webpack server by commenting out the plugins section in `capacitor.config.ts` and entering your local ip in the debugUrl field and running `yarn apply-config`. Please make sure that you start front end server with `yarn start` from 'client' directory for this to work. For examples: `yarn && yarn start -- -- --env API_HOST='http://192.168.1.205:8000` <- your local server will also need to be running.
+Debugging web app code is done by opening `chrome://inspect`, you should see the Web View when the app is running in emulator.
+When bundle is served from rust server (default setup), the js bundle is minimised and this makes debugging very hard (and doesn't allow for hot reload),
+however we can serve the bundle from the webpack server on your pc by commenting out the plugins section in `capacitor.config.ts` and entering your local ip in the debugUrl field and running `yarn apply-config`.
+Please make sure that you start front end server with `yarn start` from 'client' directory for this to work.
+For examples: `yarn && yarn start -- -- --env API_HOST='http://192.168.1.205:8000` <- your local server will also need to be running.
 
 Please note that when debugging with live reload, connections to a discovered server will always go through to the webpack server (regardless of which server is being selected).
 
diff --git a/client/packages/android/doc/choosing_OMS_Android_Gradle_JDK.png b/client/packages/android/doc/choosing_OMS_Android_Gradle_JDK.png
new file mode 100644
index 00000000000..908c69e6bc0
Binary files /dev/null and b/client/packages/android/doc/choosing_OMS_Android_Gradle_JDK.png differ
diff --git a/client/packages/android/doc/omSupply_android_gradle_jdk.png b/client/packages/android/doc/omSupply_android_gradle_jdk.png
deleted file mode 100644
index ea8ff3bdf77..00000000000
Binary files a/client/packages/android/doc/omSupply_android_gradle_jdk.png and /dev/null differ
diff --git a/client/packages/android/doc/omSupply_android_ndk.png b/client/packages/android/doc/omSupply_android_ndk.png
index eb011012eec..019ef1233ec 100644
Binary files a/client/packages/android/doc/omSupply_android_ndk.png and b/client/packages/android/doc/omSupply_android_ndk.png differ
diff --git a/client/packages/coldchain/src/Equipment/DetailView/AppBarButtons.tsx b/client/packages/coldchain/src/Equipment/DetailView/AppBarButtons.tsx
index 4a59d283cfe..5b610d0d811 100644
--- a/client/packages/coldchain/src/Equipment/DetailView/AppBarButtons.tsx
+++ b/client/packages/coldchain/src/Equipment/DetailView/AppBarButtons.tsx
@@ -32,14 +32,14 @@ export const AppBarButtonsComponent = () => {
   };
 
   const printAssetLabel = () => {
+    const date = new Date().toLocaleDateString();
     setIsPrinting(true);
     fetch(Environment.PRINT_LABEL_QR, {
       method: 'POST',
       body: JSON.stringify({
         code: data?.id,
-        message: `${t('label.serial')}: ${data?.serialNumber ?? ''}\n${t(
-          'label.asset-number'
-        )}: ${data?.assetNumber ?? ''}`,
+        assetNumber: `${data?.assetNumber ?? ''}`,
+        datePrinted: `${date}`,
       }),
       credentials: 'include',
       headers: { 'Content-Type': 'application/json' },
diff --git a/client/packages/common/src/intl/locales/en/common.json b/client/packages/common/src/intl/locales/en/common.json
index 9629309c5ce..7172a16b0da 100644
--- a/client/packages/common/src/intl/locales/en/common.json
+++ b/client/packages/common/src/intl/locales/en/common.json
@@ -718,6 +718,7 @@
   "label.date-format": "DD/MM/YYYY",
   "label.date-given": "Date given",
   "label.date-of-birth": "Date of Birth",
+  "label.date-printed": "Date Printed",
   "label.date-range": "Date Range",
   "label.date-time": "Date time",
   "label.day": "day",
diff --git a/server/server/src/print/label.rs b/server/server/src/print/label.rs
index 3d0ba86a344..c8dc7acf7e0 100644
--- a/server/server/src/print/label.rs
+++ b/server/server/src/print/label.rs
@@ -5,24 +5,18 @@ use actix_web::{
 use repository::RepositoryError;
 use service::{
     auth_data::AuthData,
-    print::label::{host_status, print_prescription_label, print_qr_code, PrescriptionLabelData},
+    print::label::{host_status, print_asset_label, print_prescription_label, AssetLabelData, PrescriptionLabelData},
     service_provider::ServiceProvider,
     settings::LabelPrinterSettingNode,
 };
 
 use crate::authentication::validate_cookie_auth;
 
-#[derive(serde::Deserialize)]
-pub struct QrLabelData {
-    code: String,
-    message: Option<String>,
-}
-
-pub async fn print_label_qr(
+pub async fn print_label_asset(
     request: HttpRequest,
     service_provider: Data<ServiceProvider>,
     auth_data: Data<AuthData>,
-    data: web::Json<QrLabelData>,
+    data: web::Json<AssetLabelData>,
 ) -> HttpResponse {
     let auth_result = validate_cookie_auth(request.clone(), &auth_data);
     match auth_result {
@@ -40,9 +34,8 @@ pub async fn print_label_qr(
                 .body(format!("Error getting printer settings: {}", error));
         }
     };
-
-    match print_qr_code(settings, data.code.clone(), data.message.clone()) {
-        Ok(_) => HttpResponse::Ok().body("QR label printed"),
+    match print_asset_label(settings, data.into_inner()) {
+        Ok(_) => HttpResponse::Ok().body("Asset label printed"),
         Err(err) => HttpResponse::InternalServerError().body(err.to_string()),
     }
 }
diff --git a/server/server/src/print/mod.rs b/server/server/src/print/mod.rs
index 199a1c02ff6..e43327bd823 100644
--- a/server/server/src/print/mod.rs
+++ b/server/server/src/print/mod.rs
@@ -1,7 +1,7 @@
 use actix_web::web;
 
 mod label;
-use label::{print_label_prescription, print_label_qr};
+use label::{print_label_prescription, print_label_asset};
 
 use self::label::test_printer;
 
@@ -10,7 +10,7 @@ const URL_PATH: &str = "/print";
 pub fn config_print(cfg: &mut web::ServiceConfig) {
     cfg.route(
         &format!("{}/label-qr", URL_PATH),
-        web::post().to(print_label_qr),
+        web::post().to(print_label_asset),
     );
     cfg.route(
         &format!("{}/label-prescription", URL_PATH),
@@ -20,4 +20,4 @@ pub fn config_print(cfg: &mut web::ServiceConfig) {
         &format!("{}/label-test", URL_PATH),
         web::post().to(test_printer),
     );
-}
+}
\ No newline at end of file
diff --git a/server/service/src/print/label.rs b/server/service/src/print/label.rs
index 0f326005f3b..74b4997140c 100644
--- a/server/service/src/print/label.rs
+++ b/server/service/src/print/label.rs
@@ -4,44 +4,53 @@ use crate::settings::LabelPrinterSettingNode;
 
 use super::jetdirect::{Jetdirect, Mode};
 
-const LINE_HEIGHT_IN_DOTS: i32 = 50;
+#[derive(serde::Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct AssetLabelData {
+    code: String,
+    asset_number: Option<String>,
+    date_printed: Option<String>
+}
 
-pub fn print_qr_code(
+pub fn print_asset_label(
     settings: LabelPrinterSettingNode,
-    code: String,
-    message: Option<String>,
+    data: AssetLabelData,
 ) -> Result<String> {
-    let qr_height = 133; // approx height in dots for the magnification factor of 4 when printing a uuid
-    let vertical_offset = (settings.label_height - qr_height) / 2;
-    let formatted_message = match message {
-        Some(msg) => {
-            // adding max to ensure that the y is not negative
-            let mut y = (vertical_offset - LINE_HEIGHT_IN_DOTS).max(0);
-
-            msg.split('\n')
-                .map(|line| {
-                    y += LINE_HEIGHT_IN_DOTS;
-                    format!("^FO200,{}^A0,32,25^FD{}^FS", y, line)
-                })
-                .collect::<Vec<_>>()
-                .join("\n")
-        }
-        None => "".to_string(),
-    };
 
+    let asset_number = data.asset_number.unwrap_or_default();
+
+    let date_printed = data.date_printed.unwrap_or_default();
+
+    let m_supply_logo = "^FO265,114^GFA,6360,6360,40,,:::::::::::::V07,V0F,U01F,U07F,U0FF,T03FE,T0FFE,S03FFE,S07FFE,S07FFC,R01IFC,Q01JFC,Q0KFC,P07IF9IF,O01JF0FFE,O07IFC1FFE,O0JFCIF8,N03JFBIF,N07NF,:N0OF,M01OF8003FFE,M01OFC003FFE,M01OFE003IF,:N0OFE003FFE,N07NFC001FFC,N03NFCI0FF8,N01NFC001FFC,O0NF8003FFE,O03MF800JF8,O01MF800JF8,P0MF800JF8,P07LFI0JF8,::P03LFI0JF8,P03KFEI0JF8,P01KF8I0JF8,Q0KFJ0JF8,Q0EJFJ0JF8,Q0E1IFCI0JF8,P07FC7FFEI0JF8,O01LFEI0JF8,O07MFI0JF8,O0NF800JF8,N03NFC00JF8,N07NFC00JF8,N0OFE007IF,M01OFE,M01PF,M03PF,M03IFE3KFL07,M03JF00IFE42I03E,M03JFE03FFEEKF8,M03KFC3FFCKFE,M03KFE3FFDKF8,M07KFC3FFBJFE,M07JFE03FF7JF8,M07JF801FEJFC,M07LF079IFE,M07KFE007IFM07C3F87C7F,M07KFCC0IFCM0E639CEE7F8,M07LFCJFCM0C330CEE718,M07LFDJFEL018330CFE718,M07QFEL01C330CE0618,M07QFEM0C739CE0618,M07QFCM0FE3F87E618,M03QF8M03C3F03C2,M03OFCFP03,M03OF9EP03P03C,M01OF1CP01O03FFCgJ01E,N0NFE38g07IFgJ03E,N07MF838Y01JF8gI03E,N03MF078Y03JF8gI03E,O0LF80Fg03F81F8gI03E,O03JFE03Fg07E007gJ03E,P0JF007Fg07CgM03E,O060FF001FEg0FCgM03E,O03CJ0FFEg0FCgM03E,O03FE01JFR0EI0EI0FCU038L0EI03E,O03NFEN0F07F807FC00FCK0F8003E01E1FFI0787FC003E07CI0F8,O03OFN0F1FFE0IF007CK0F8003E01E7FFC0079IF003E07C001F8,O07OFCM0F3FFE1IF007EK0F8003E03JFE00KF803E07E001F,O07MFCFEM0KF3IF803F8J0F8003E03KF00KFC03E03E001F,O07MF87FM0FF83FFC1F803FCJ0F8003E03FE03F80FF80FE03E03E003E,O07MF83F8L0FF01FF00FC01FF8I0F8003E03FC01F80FF007E03E03F003E,O07MF01FCL0FE00FF007C00FFEI0F8003E03F800FC0FE003E03E01F003E,O07LFE00FEL0FC00FE007C003FF800F8003E03FI07C0FC001F03E01F807C,O0MFC387EL0F800FC007CI0FFE00F8003E03FI07C0FC001F03E00F807C,N01MFC787FL0F800FC007CI03FF00F8003E03FI07C0FC001F03E00F807C,N01MF8703F8K0F800FC007CJ0FF80F8003E03EI03E0F8I0F03E007C0F8,N01MF0E01F8K0F800F8007CJ03F80F8003E03EI03E0F8I0F03E007C0F8,N01MF3C71FCK0F800F8007CK0FC0F8003E03EI03E0F8I0F03E007C1F,N03NFDF0FCK0FI0F8007CK07C0F8003E03EI03E0F8I0F03E003E1F,N03OFE0FCK0FI0F8007CK07C0F8003E03EI03E0F8I0F03E003E1E,N03OFE07EK0FI0F8007CK07E0F8007E03FI07C0FC001F03E001F3E,N07PFE7EK0FI0F8007CK07E0F8007E03FI07C0FC001F03E001F3E,I01CI07PFE7EK0FI0F8007CK07C0F8007E03FI07C0FC001F03EI0F7C,I03UFC7EK0FI0F8007C02I07C0FC00FE03F800FC0FE003F03EI0FFC,I03UFE7EK0FI0F8007C07800FC07C01FE03FC01F80FF007E03EI0FFC,I03UFE7FK0FI0F8007C0FE01F807E03FE03FE03F80FF80FE01FI07F8,I03UFE7FK0FI0F8007C0KF807JFE03FF8FF00FFE3FC01FC007F8,I07VF7FK0FI0F8007C07JF003IFBE03JFE00KF801FE003F,I07PF9MFK0FI0F8007C03IFE003IF3E03F7FFC00FDIFI0FE003F,I07PF8MFK0FI078007C00IF8001FFC3E03E3FF800F8FFEI07E003F,I07PF07LFW01FEJ03FJ03E0FCI0F83FK08003E,I07OFE03LFgP03EL0F8P03E,I07OFC01KFEgP03EL0F8P03C,I03OF801KFCgP03EL0F8P07C,I01OFI0KFCgP03EL0F8P07C,I01NFEI0KFCgP03EL0F8P0F8,I01NFCI07JF8gP03EL0F8N033F8,J0NF8I07JF8gP03EL0F8N07FF,J0NFJ07JF8gP03EL0F8N07FE,J0NFJ07JF8gP01EL078N07FC,J0MFEJ03JF87F8hK01E,J0MFEJ03MFE,J0MFCJ03MFE,J0FE003FFCJ03MFC,J0FCP07MF8,J0FCP07KF,J0FCP07JF8,J0F8P07IFE,I01FQ07IF8,I01EQ0IFE,I01CQ0IF8,I01CQ07FE,I018Q07F8,I018Q0FC,I01R04,,:::::::::::::^FS";
+    
     let payload = format!(
         r#"
         ^XA
         ^FX CI command parameters:
         ^FX - encoding (28 = UTF-8)
         ^CI28
-        ^FO50,{}
-        ^BQN,2,4
-        ^FDMA,{}^FS        
+        ^FO18,18
+        ^BXN,11,200
+        ^FD{}^FS
+        ^FO277,18^A0,32,25^FD{}^FS
+        ^FO277,73^A0,32,25^FD{}^FS
         {}
         ^XZ"#,
-        vertical_offset, code, formatted_message
+        data.code, asset_number, date_printed, m_supply_logo
     );
+
+    // To output QR codes rather than data matrix barcodes:
+    // In the payload variable above, there are these three lines of code:
+    // ^FO18,18
+    // ^BXN,11,200
+    // ^FD{}^FS
+    // Replace them with these three lines of code:
+    // ^FO22,12
+    // ^BQN,2,8
+    // ^FDMA,{}^FS
+    // For an explanation of how this works, see https://supportcommunity.zebra.com/s/article/ZPL-Command-Information-and-DetailsV2?language=en_US
+    // And if you want to make the new label look its best, play with the spacing of things at labelary.com/viewer.html
+
     let printer = Jetdirect::new(settings.address, settings.port);
     printer.send_string(payload, Mode::Print)
 }


Reviews:
user: login: jmbrunskill
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T20:30:37Z
user: login: jmbrunskill
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T20:32:10Z
user: login: jmbrunskill
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T21:50:00Z
user: login: jmbrunskill
body: Just that one quick fix for the `toLocalDateString()` function, otherwise looking good!
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T21:51:14Z
user: login: jmbrunskill
body: Thanks!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T02:45:54Z
user: login: jmbrunskill
body: Actually I'm not sure we should check in the .idea folder.
I'm open to the idea but want to check if it might cause problems...
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T02:48:25Z
user: login: jmbrunskill
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T21:42:03Z
user: login: jmbrunskill
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T21:44:07Z
user: login: jmbrunskill
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T21:46:40Z
user: login: jmbrunskill
body: Thanks Zachariah.
I've also pushed a quick fix for the warning messages in Rust
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T00:59:40Z
user: login: jmbrunskill
body: Looks good to me, made some updates to the docs. Can you double check I haven't said anything dumb...
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T03:19:57Z
user: login: jmbrunskill
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T03:22:22Z
user: login: zachariah-at-msupply
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T04:03:16Z
user: login: jmbrunskill
body: Looks great thanks @zachariah-at-msupply 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-26T22:45:50Z

Review comments:
diff_hunk: @@ -32,14 +32,13 @@ export const AppBarButtonsComponent = () => {
   };
 
   const printAssetLabel = () => {
+    const date = new Date().toLocaleString().split(', ')[0];
     setIsPrinting(true);
     fetch(Environment.PRINT_LABEL_QR, {
       method: 'POST',
       body: JSON.stringify({
         code: data?.id,
-        message: `${t('label.serial')}: ${data?.serialNumber ?? ''}\n${t(
-          'label.asset-number'
-        )}: ${data?.assetNumber ?? ''}`,
+        message: `${t('label.serial')}: ${data?.serialNumber ?? ''}\n${t('label.asset-number')}: ${data?.assetNumber ?? ''}\n${t('label.date-printed')}: ${date}`,
path: client/packages/coldchain/src/Equipment/DetailView/AppBarButtons.tsx
user: login: jmbrunskill
body: ```suggestion
        message: `${t('label.asset-number')}: ${data?.assetNumber ?? ''}\n${t('label.date-printed')}: ${date}`,
```
After discussion with @adamdewey we think it's best just to have the asset number as they're probably going to be set to the same thing, and serial number should already be on the device
created_at: 2025-03-17T20:30:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -28,20 +28,32 @@ pub fn print_qr_code(
         }
         None => "".to_string(),
     };
-
+    let mSupply_logo = "
+^FO395,217^GFA,1500,1500,25,,::::::N03,N0F,M03E,M0F38,L01FF,L03FF,L03FF87,L01FF07O01F8T038,M0FF1FCN07FET03C,M07F1FCN0F07T01C,M04E1FCN0C03U0C,M0FF07O0C03U0C,L03FF87N01CW0C,L03FF8P0C001C00C38780E1E00C6001,L03F7FFI03C0F00E001E00C79FE0F7F80C7003,L07F3F801EFE3F80FI0E00C1F0707E1C0C3007,L07F9EI0FC771C07C00600C1E038380E0C1806,L07FFEI0783E0C01F00600C1C01838060C180E,L03FF4I0703C0C007C0600C1C01830070C1C0C,L03FE8I060180E001E0600C1C01C30030C0C0C,M0F98I060180EI070600C1C01C30030C0C18,M0FFCI060180EI030600C1C01C30030C0618,M0FF6I060180EI030600C1C01C30030C0638,L01FF3I060180EI030601C1C01830070C033,L01FE180060180EI030601C1C01830060C033,L01FFC80060180E08030603C1E038380E0C03E,K07IFE80060180E1E070706C1F0703C0C0C01E,K07IFE80060180E0FFE03FCC1FFE037FC0C01E,K07FF3F80060180E03F801F8C1CFC033F00C00C,K07FE1F8W01CI03M0C,K03FE1F8W01CI03L018,K03FC1FFW01CI03L018,K02001F8W01CI03L03,K02001EX01CI03K077,K040018X01CI03K07E,,:::::::::::::::::^FS";
+    
     let payload = format!(
         r#"
         ^XA
         ^FX CI command parameters:
         ^FX - encoding (28 = UTF-8)
         ^CI28
         ^FO50,{}
-        ^BQN,2,4
-        ^FDMA,{}^FS        
+        ^BXN,6,200
path: server/service/src/print/label.rs
user: login: jmbrunskill
body: Can we see if we can make this logo a bit bigger? Seems like there's margin as the mSupply Logo is below it.
Also I can't seem to scan it so something's not quite right. Not 100% sure what the issue is, it could just be that our code to detect GS1 vs our own barcode needs to be updated now?
created_at: 2025-03-17T20:32:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -32,14 +32,14 @@ export const AppBarButtonsComponent = () => {
   };
 
   const printAssetLabel = () => {
+    const date = new Date().toLocaleString().split(', ')[0];
path: client/packages/coldchain/src/Equipment/DetailView/AppBarButtons.tsx
user: login: jmbrunskill
body: ```suggestion
    const date = new Date().toLocaleDateString();
```
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleDateString

I think this is a safer thing to do, I'm not sure if all languages will work with the split function...
created_at: 2025-03-18T21:50:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
path: server/android/.idea/.gitignore
user: login: jmbrunskill
body: Can you add
usage.statistics.xml

created_at: 2025-03-19T21:42:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
path: server/android/.idea/.gitignore
user: login: jmbrunskill
body: Actually maybe we just replace this with 
https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
?

I've had a read of https://intellij-support.jetbrains.com/hc/en-us/articles/206544839-How-to-manage-projects-under-Version-Control-Systems which seems to suggest it's ok to check in what you're checking in here...
created_at: 2025-03-19T21:44:07Z
author_association: CONTRIBUTOR
path: server/android/.idea/caches/deviceStreaming.xml
user: login: jmbrunskill
body: Pretty sure we don't want to include anything under `caches` even through it's not mentioned in that article.
I'd ignore the caches directory too, unless you know a good reason it should be included?
created_at: 2025-03-19T21:46:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,44 +4,53 @@ use crate::settings::LabelPrinterSettingNode;
 
 use super::jetdirect::{Jetdirect, Mode};
 
-const LINE_HEIGHT_IN_DOTS: i32 = 50;
+#[derive(serde::Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct AssetLabelData {
+    code: String,
+    asset_number: Option<String>,
+    date_printed: Option<String>
+}
 
-pub fn print_qr_code(
+pub fn print_asset_label(
     settings: LabelPrinterSettingNode,
-    code: String,
-    message: Option<String>,
+    data: AssetLabelData,
 ) -> Result<String> {
-    let qr_height = 133; // approx height in dots for the magnification factor of 4 when printing a uuid
-    let vertical_offset = (settings.label_height - qr_height) / 2;
-    let formatted_message = match message {
-        Some(msg) => {
-            // adding max to ensure that the y is not negative
-            let mut y = (vertical_offset - LINE_HEIGHT_IN_DOTS).max(0);
-
-            msg.split('\n')
-                .map(|line| {
-                    y += LINE_HEIGHT_IN_DOTS;
-                    format!("^FO200,{}^A0,32,25^FD{}^FS", y, line)
-                })
-                .collect::<Vec<_>>()
-                .join("\n")
-        }
-        None => "".to_string(),
-    };
 
+    let asset_number = data.asset_number.unwrap_or_default();
+
+    let date_printed = data.date_printed.unwrap_or_default();
+
+    let m_supply_logo = "^FO265,114^GFA,6360,6360,40,,:::::::::::::V07,V0F,U01F,U07F,U0FF,T03FE,T0FFE,S03FFE,S07FFE,S07FFC,R01IFC,Q01JFC,Q0KFC,P07IF9IF,O01JF0FFE,O07IFC1FFE,O0JFCIF8,N03JFBIF,N07NF,:N0OF,M01OF8003FFE,M01OFC003FFE,M01OFE003IF,:N0OFE003FFE,N07NFC001FFC,N03NFCI0FF8,N01NFC001FFC,O0NF8003FFE,O03MF800JF8,O01MF800JF8,P0MF800JF8,P07LFI0JF8,::P03LFI0JF8,P03KFEI0JF8,P01KF8I0JF8,Q0KFJ0JF8,Q0EJFJ0JF8,Q0E1IFCI0JF8,P07FC7FFEI0JF8,O01LFEI0JF8,O07MFI0JF8,O0NF800JF8,N03NFC00JF8,N07NFC00JF8,N0OFE007IF,M01OFE,M01PF,M03PF,M03IFE3KFL07,M03JF00IFE42I03E,M03JFE03FFEEKF8,M03KFC3FFCKFE,M03KFE3FFDKF8,M07KFC3FFBJFE,M07JFE03FF7JF8,M07JF801FEJFC,M07LF079IFE,M07KFE007IFM07C3F87C7F,M07KFCC0IFCM0E639CEE7F8,M07LFCJFCM0C330CEE718,M07LFDJFEL018330CFE718,M07QFEL01C330CE0618,M07QFEM0C739CE0618,M07QFCM0FE3F87E618,M03QF8M03C3F03C2,M03OFCFP03,M03OF9EP03P03C,M01OF1CP01O03FFCgJ01E,N0NFE38g07IFgJ03E,N07MF838Y01JF8gI03E,N03MF078Y03JF8gI03E,O0LF80Fg03F81F8gI03E,O03JFE03Fg07E007gJ03E,P0JF007Fg07CgM03E,O060FF001FEg0FCgM03E,O03CJ0FFEg0FCgM03E,O03FE01JFR0EI0EI0FCU038L0EI03E,O03NFEN0F07F807FC00FCK0F8003E01E1FFI0787FC003E07CI0F8,O03OFN0F1FFE0IF007CK0F8003E01E7FFC0079IF003E07C001F8,O07OFCM0F3FFE1IF007EK0F8003E03JFE00KF803E07E001F,O07MFCFEM0KF3IF803F8J0F8003E03KF00KFC03E03E001F,O07MF87FM0FF83FFC1F803FCJ0F8003E03FE03F80FF80FE03E03E003E,O07MF83F8L0FF01FF00FC01FF8I0F8003E03FC01F80FF007E03E03F003E,O07MF01FCL0FE00FF007C00FFEI0F8003E03F800FC0FE003E03E01F003E,O07LFE00FEL0FC00FE007C003FF800F8003E03FI07C0FC001F03E01F807C,O0MFC387EL0F800FC007CI0FFE00F8003E03FI07C0FC001F03E00F807C,N01MFC787FL0F800FC007CI03FF00F8003E03FI07C0FC001F03E00F807C,N01MF8703F8K0F800FC007CJ0FF80F8003E03EI03E0F8I0F03E007C0F8,N01MF0E01F8K0F800F8007CJ03F80F8003E03EI03E0F8I0F03E007C0F8,N01MF3C71FCK0F800F8007CK0FC0F8003E03EI03E0F8I0F03E007C1F,N03NFDF0FCK0FI0F8007CK07C0F8003E03EI03E0F8I0F03E003E1F,N03OFE0FCK0FI0F8007CK07C0F8003E03EI03E0F8I0F03E003E1E,N03OFE07EK0FI0F8007CK07E0F8007E03FI07C0FC001F03E001F3E,N07PFE7EK0FI0F8007CK07E0F8007E03FI07C0FC001F03E001F3E,I01CI07PFE7EK0FI0F8007CK07C0F8007E03FI07C0FC001F03EI0F7C,I03UFC7EK0FI0F8007C02I07C0FC00FE03F800FC0FE003F03EI0FFC,I03UFE7EK0FI0F8007C07800FC07C01FE03FC01F80FF007E03EI0FFC,I03UFE7FK0FI0F8007C0FE01F807E03FE03FE03F80FF80FE01FI07F8,I03UFE7FK0FI0F8007C0KF807JFE03FF8FF00FFE3FC01FC007F8,I07VF7FK0FI0F8007C07JF003IFBE03JFE00KF801FE003F,I07PF9MFK0FI0F8007C03IFE003IF3E03F7FFC00FDIFI0FE003F,I07PF8MFK0FI078007C00IF8001FFC3E03E3FF800F8FFEI07E003F,I07PF07LFW01FEJ03FJ03E0FCI0F83FK08003E,I07OFE03LFgP03EL0F8P03E,I07OFC01KFEgP03EL0F8P03C,I03OF801KFCgP03EL0F8P07C,I01OFI0KFCgP03EL0F8P07C,I01NFEI0KFCgP03EL0F8P0F8,I01NFCI07JF8gP03EL0F8N033F8,J0NF8I07JF8gP03EL0F8N07FF,J0NFJ07JF8gP03EL0F8N07FE,J0NFJ07JF8gP01EL078N07FC,J0MFEJ03JF87F8hK01E,J0MFEJ03MFE,J0MFCJ03MFE,J0FE003FFCJ03MFC,J0FCP07MF8,J0FCP07KF,J0FCP07JF8,J0F8P07IFE,I01FQ07IF8,I01EQ0IFE,I01CQ0IF8,I01CQ07FE,I018Q07F8,I018Q0FC,I01R04,,:::::::::::::^FS";
+    
     let payload = format!(
         r#"
         ^XA
         ^FX CI command parameters:
         ^FX - encoding (28 = UTF-8)
         ^CI28
-        ^FO50,{}
-        ^BQN,2,4
-        ^FDMA,{}^FS        
+        ^FO18,18
+        ^BXN,11,200
+        ^FD{}^FS
+        ^FO277,18^A0,32,25^FDAsset Number: {}^FS
path: server/service/src/print/label.rs
user: login: jmbrunskill
body: One issue I've realised with your change is that we can't translate these fields.
At the moment I think it's ok, but in future we might need to find a way to translate this?
created_at: 2025-03-26T03:22:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,44 +4,53 @@ use crate::settings::LabelPrinterSettingNode;
 
 use super::jetdirect::{Jetdirect, Mode};
 
-const LINE_HEIGHT_IN_DOTS: i32 = 50;
+#[derive(serde::Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct AssetLabelData {
+    code: String,
+    asset_number: Option<String>,
+    date_printed: Option<String>
+}
 
-pub fn print_qr_code(
+pub fn print_asset_label(
     settings: LabelPrinterSettingNode,
-    code: String,
-    message: Option<String>,
+    data: AssetLabelData,
 ) -> Result<String> {
-    let qr_height = 133; // approx height in dots for the magnification factor of 4 when printing a uuid
-    let vertical_offset = (settings.label_height - qr_height) / 2;
-    let formatted_message = match message {
-        Some(msg) => {
-            // adding max to ensure that the y is not negative
-            let mut y = (vertical_offset - LINE_HEIGHT_IN_DOTS).max(0);
-
-            msg.split('\n')
-                .map(|line| {
-                    y += LINE_HEIGHT_IN_DOTS;
-                    format!("^FO200,{}^A0,32,25^FD{}^FS", y, line)
-                })
-                .collect::<Vec<_>>()
-                .join("\n")
-        }
-        None => "".to_string(),
-    };
 
+    let asset_number = data.asset_number.unwrap_or_default();
+
+    let date_printed = data.date_printed.unwrap_or_default();
+
+    let m_supply_logo = "^FO265,114^GFA,6360,6360,40,,:::::::::::::V07,V0F,U01F,U07F,U0FF,T03FE,T0FFE,S03FFE,S07FFE,S07FFC,R01IFC,Q01JFC,Q0KFC,P07IF9IF,O01JF0FFE,O07IFC1FFE,O0JFCIF8,N03JFBIF,N07NF,:N0OF,M01OF8003FFE,M01OFC003FFE,M01OFE003IF,:N0OFE003FFE,N07NFC001FFC,N03NFCI0FF8,N01NFC001FFC,O0NF8003FFE,O03MF800JF8,O01MF800JF8,P0MF800JF8,P07LFI0JF8,::P03LFI0JF8,P03KFEI0JF8,P01KF8I0JF8,Q0KFJ0JF8,Q0EJFJ0JF8,Q0E1IFCI0JF8,P07FC7FFEI0JF8,O01LFEI0JF8,O07MFI0JF8,O0NF800JF8,N03NFC00JF8,N07NFC00JF8,N0OFE007IF,M01OFE,M01PF,M03PF,M03IFE3KFL07,M03JF00IFE42I03E,M03JFE03FFEEKF8,M03KFC3FFCKFE,M03KFE3FFDKF8,M07KFC3FFBJFE,M07JFE03FF7JF8,M07JF801FEJFC,M07LF079IFE,M07KFE007IFM07C3F87C7F,M07KFCC0IFCM0E639CEE7F8,M07LFCJFCM0C330CEE718,M07LFDJFEL018330CFE718,M07QFEL01C330CE0618,M07QFEM0C739CE0618,M07QFCM0FE3F87E618,M03QF8M03C3F03C2,M03OFCFP03,M03OF9EP03P03C,M01OF1CP01O03FFCgJ01E,N0NFE38g07IFgJ03E,N07MF838Y01JF8gI03E,N03MF078Y03JF8gI03E,O0LF80Fg03F81F8gI03E,O03JFE03Fg07E007gJ03E,P0JF007Fg07CgM03E,O060FF001FEg0FCgM03E,O03CJ0FFEg0FCgM03E,O03FE01JFR0EI0EI0FCU038L0EI03E,O03NFEN0F07F807FC00FCK0F8003E01E1FFI0787FC003E07CI0F8,O03OFN0F1FFE0IF007CK0F8003E01E7FFC0079IF003E07C001F8,O07OFCM0F3FFE1IF007EK0F8003E03JFE00KF803E07E001F,O07MFCFEM0KF3IF803F8J0F8003E03KF00KFC03E03E001F,O07MF87FM0FF83FFC1F803FCJ0F8003E03FE03F80FF80FE03E03E003E,O07MF83F8L0FF01FF00FC01FF8I0F8003E03FC01F80FF007E03E03F003E,O07MF01FCL0FE00FF007C00FFEI0F8003E03F800FC0FE003E03E01F003E,O07LFE00FEL0FC00FE007C003FF800F8003E03FI07C0FC001F03E01F807C,O0MFC387EL0F800FC007CI0FFE00F8003E03FI07C0FC001F03E00F807C,N01MFC787FL0F800FC007CI03FF00F8003E03FI07C0FC001F03E00F807C,N01MF8703F8K0F800FC007CJ0FF80F8003E03EI03E0F8I0F03E007C0F8,N01MF0E01F8K0F800F8007CJ03F80F8003E03EI03E0F8I0F03E007C0F8,N01MF3C71FCK0F800F8007CK0FC0F8003E03EI03E0F8I0F03E007C1F,N03NFDF0FCK0FI0F8007CK07C0F8003E03EI03E0F8I0F03E003E1F,N03OFE0FCK0FI0F8007CK07C0F8003E03EI03E0F8I0F03E003E1E,N03OFE07EK0FI0F8007CK07E0F8007E03FI07C0FC001F03E001F3E,N07PFE7EK0FI0F8007CK07E0F8007E03FI07C0FC001F03E001F3E,I01CI07PFE7EK0FI0F8007CK07C0F8007E03FI07C0FC001F03EI0F7C,I03UFC7EK0FI0F8007C02I07C0FC00FE03F800FC0FE003F03EI0FFC,I03UFE7EK0FI0F8007C07800FC07C01FE03FC01F80FF007E03EI0FFC,I03UFE7FK0FI0F8007C0FE01F807E03FE03FE03F80FF80FE01FI07F8,I03UFE7FK0FI0F8007C0KF807JFE03FF8FF00FFE3FC01FC007F8,I07VF7FK0FI0F8007C07JF003IFBE03JFE00KF801FE003F,I07PF9MFK0FI0F8007C03IFE003IF3E03F7FFC00FDIFI0FE003F,I07PF8MFK0FI078007C00IF8001FFC3E03E3FF800F8FFEI07E003F,I07PF07LFW01FEJ03FJ03E0FCI0F83FK08003E,I07OFE03LFgP03EL0F8P03E,I07OFC01KFEgP03EL0F8P03C,I03OF801KFCgP03EL0F8P07C,I01OFI0KFCgP03EL0F8P07C,I01NFEI0KFCgP03EL0F8P0F8,I01NFCI07JF8gP03EL0F8N033F8,J0NF8I07JF8gP03EL0F8N07FF,J0NFJ07JF8gP03EL0F8N07FE,J0NFJ07JF8gP01EL078N07FC,J0MFEJ03JF87F8hK01E,J0MFEJ03MFE,J0MFCJ03MFE,J0FE003FFCJ03MFC,J0FCP07MF8,J0FCP07KF,J0FCP07JF8,J0F8P07IFE,I01FQ07IF8,I01EQ0IFE,I01CQ0IF8,I01CQ07FE,I018Q07F8,I018Q0FC,I01R04,,:::::::::::::^FS";
+    
     let payload = format!(
         r#"
         ^XA
         ^FX CI command parameters:
         ^FX - encoding (28 = UTF-8)
         ^CI28
-        ^FO50,{}
-        ^BQN,2,4
-        ^FDMA,{}^FS        
+        ^FO18,18
+        ^BXN,11,200
+        ^FD{}^FS
+        ^FO277,18^A0,32,25^FDAsset Number: {}^FS
path: server/service/src/print/label.rs
user: login: zachariah-at-msupply
body: Hmm, I refactored that to fix it, and uncovered some additional issues with translations:
- it seems that some translations have much longer ways of saying 'asset number' and 'date printed' than others. They push the asset number itself and the date off the label. 
- It seems that some languages, like Arabic, use characters that the printer cannot print in this configuration.
created_at: 2025-03-26T04:03:16Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-13T23:02:22Z
body: <!-- GITHUB_ACTION_COMMENT_PR -->
## Bundle size difference
Comparing this PR to `main`

|Old size      | New size     | Diff                     |
|:------------ |:-------------|:-------------------------|
|6.1 MB         |6.1 MB         | 25 B (0.00%) |

author_association: CONTRIBUTOR
user: login: adamdewey
created_at: 2025-03-13T23:11:10Z
body: @zachariah-at-msupply - is it possible to put the text information and logo below the data matrix?

That way we can print the data matrix as large as possible

There are concerns that some phone cameras have issues scanning the data matrix when it's small

Thanks
author_association: CONTRIBUTOR
user: login: jmbrunskill
created_at: 2025-03-16T23:14:37Z
body: Lets remove serial numbe as it's already included on the device
author_association: CONTRIBUTOR
user: login: zachariah-at-msupply
created_at: 2025-03-17T20:11:44Z
body: > @zachariah-at-msupply - is it possible to put the text information and logo below the data matrix?
> 
> That way we can print the data matrix as large as possible
> 
> There are concerns that some phone cameras have issues scanning the data matrix when it's small
> 
> Thanks

Absolutely, I can do that!
I think that the way to make the barcode as large as possible may be to keep it on one side however - like this:
![image](https://github.com/user-attachments/assets/86f8b92f-1095-46df-82c1-e6a51c90d5ef)

What do you think?
author_association: CONTRIBUTOR
user: login: jmbrunskill
created_at: 2025-03-17T22:15:00Z
body: > > @zachariah-at-msupply - is it possible to put the text information and logo below the data matrix?
> > That way we can print the data matrix as large as possible
> > There are concerns that some phone cameras have issues scanning the data matrix when it's small
> > Thanks
> 
> Absolutely, I can do that! I think that the way to make the barcode as large as possible may be to keep it on one side however - like this: ![image](https://private-user-images.githubusercontent.com/188113868/423662841-86f8b92f-1095-46df-82c1-e6a51c90d5ef.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDIyNDk5NzQsIm5iZiI6MTc0MjI0OTY3NCwicGF0aCI6Ii8xODgxMTM4NjgvNDIzNjYyODQxLTg2ZjhiOTJmLTEwOTUtNDZkZi04MmMxLWU2YTUxYzkwZDVlZi5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjUwMzE3JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI1MDMxN1QyMjE0MzRaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1mZDFlZTU4NzQwNzFiYTQ0OTRmOWE4Mjk1YTY0ZTJiZTA0YTJhYTk1NTZiMWNjNTg0ZWY0N2UxODcyNWJhNTM4JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.FjP0rJ_kZOOn97ezVsofZZSz4BwM5p3H7L7DSIy___Q)
> 
> What do you think?

Looks great
author_association: CONTRIBUTOR
user: login: adamdewey
created_at: 2025-03-17T23:09:56Z
body: Yep that looks great @zachariah-at-msupply 🔥 
author_association: CONTRIBUTOR
user: login: zachariah-at-msupply
created_at: 2025-03-17T23:54:35Z
body: I updated the PR's initial comment with this update:
- The logo is now a big, proud, proper Open mSupply logo - huzzah!
- The label now looks like this:
![telegram-cloud-photo-size-1-5100522897113460621-y](https://github.com/user-attachments/assets/ef5e8e90-4142-4ee8-b38c-75fc540abb98)

author_association: CONTRIBUTOR
user: login: zachariah-at-msupply
created_at: 2025-03-26T22:38:09Z
body: I updated the label to fix the problem with the translation, documented in the comment above and in issue [7065](https://github.com/msupply-foundation/open-msupply/issues/7065).
As Mark specified in that issue, the labels now look like this (without asset number or date printed labels):

![telegram-cloud-photo-size-1-5127633731413191889-y](https://github.com/user-attachments/assets/3dd718e8-21b0-4dce-8b49-652c938ca754)



author_association: CONTRIBUTOR
user: login: zachariah-at-msupply
created_at: 2025-03-26T22:48:42Z
body: And to think it only took 2 weeks in the end 😅
author_association: CONTRIBUTOR