DentalZaikoMaker — Full Changelog
=================================

Complete version history. The most recent releases are also listed in readme.txt.

= 1.26.0 =

* Saving the connection settings now runs the connection test in the same step, so the status card reflects the result immediately instead of asking for a separate test.
* Notification e-mails can be sent to several addresses (one per line or comma-separated), and the daily check can be switched between daily and weekly.
* The staff PWA login is now rate-limited: repeated failures from the same address or IP are locked out for a short period. Newly set or changed PINs must be six digits; existing shorter PINs can still sign in.
* Security: the API token is no longer read from the plaintext pending option after setup — any leftover plaintext is migrated to encrypted storage on use. The audit log no longer stores the PIN hash. When AUTH_KEY is undefined, a site-specific random salt is generated for token encryption instead of a shared constant.
* Camera scanning now prompts to type the code in by hand when nothing is read for a while, since some small packages carry a barcode the bundled decoder cannot read.
* CSV import recognises a "supplier product code" column, so materials keyed by a supplier's own code rather than a JAN code are matched to the same material on re-import.

= 1.25.0 =

* Merges the WordPress.org review work (1.21.6) with the features released to clinics in 1.22.x–1.24.0, so both lines are one code base again.
* Supplier records now carry a corporate number (13 digits) and an office name. Entering a corporate number looks up the companion web service and links a registered distributor automatically.
* The daily low-stock e-mail now carries a link to the reorder list, and ordering marks the material as "on order" so the e-mail stops until the stock is received (it is released automatically on stock-in, or after 30 days).
* The reorder list can pause a material's alert or change its reorder point directly.
* Notification list: search, filter, sort and pagination.
* Supplier saving moved to admin-post, so it no longer depends on output buffering.
* Fix: activating the plugin on a site that already had data stamped the database version to the newest value without running the version-specific migrations, so any migration step that dbDelta cannot express was skipped for good. Activation now goes through the same upgrade path as a normal update. In practice this meant the supplier table kept its old unique key, so a supplier could not be registered twice under the same name with different office names — which is exactly what the corporate-number release added the office name for.
* Readme fix: the feature list said the plugin has "three role tiers: director, staff lead, and staff". No such roles exist. The three roles are administrator, staff and view-only, as the plugin's own screens have always said. There is no "staff lead" role, and the "view-only" role was missing from the list.
* The product category grouping on the stock list now matches the companion web service. The "medicine" group is relabelled from "歯科用医薬品" to "医薬品", 68 keywords the web service had learned since this classifier was written are applied (bone graft materials, radiographic accessories, laboratory casting supplies, hand instruments and others), and a material whose generic name is empty is now grouped by its product name instead of being left unclassified. The same material could previously appear under one group here and another one on the web.

= 1.21.6 =

* Camera scanning no longer uses WebAssembly. The decoder is now the bundled pure-JavaScript @zxing/library, so the package contains no binary, has no optional component to install, and references no external host. It reads JAN/EAN, GS1-128, GS1 DataBar, GS1 DataBar Expanded, DataMatrix and QR. GS1 DataBar Limited is no longer decoded by the camera and is typed in by hand.
* Security fix: the staff PWA's offline snapshot endpoint returned stock summaries for every active branch to any signed-in PWA user. It is now authorised per branch, like every other screen, so staff and view-only accounts see only their own branch.
* Security fix: the staff PWA's stock use/dispose handler authorised the submitted branch but then loaded the lot by its id alone, so a staff account could decrement or dispose a lot belonging to a branch it has no access to. The lot is now always looked up bound to the already-authorised branch and material.
* Security fix: when registering a material from the staff PWA, the submitted image attachment id was trusted as-is. It is now accepted only if it is a pending upload made by the same operator.
* The PWA's stylesheet and page scripts are now served directly from the plugin directory with wp_enqueue_style/wp_enqueue_script instead of being streamed through a PHP route. The Service Worker is still returned by PHP, because it needs the Service-Worker-Allowed header to take /dzm-pwa/ as its scope.
* Login to the staff PWA is now CSRF-protected with a nonce, and all display parameters on PWA screens are read through registered query vars or a nonce-checked helper rather than from $_GET inside the templates.
* Input from $_POST is sanitized where it is read (email with sanitize_email, and so on) instead of later on save.
* The plugin no longer calls base64_encode() or base64_decode() anywhere. The encrypted API token is stored as hexadecimal, and the connection key's base64url payload is decoded with PHP's own sodium_base642bin().
* New: the settings screen now has a "revoke" button that disables the external service integration. It deletes the stored consent and access tokens, after which the plugin issues no API request at all — including the scheduled tasks. The plugin's own screens and readme said this was possible, but no such control existed. Local inventory data is kept.
* Fixed: redeeming an application code contacted the web service before consent had been given. It is now gated on consent like every other request, so the plugin really does send nothing until the administrator agrees.
* Fixed: on the "needs reordering" list, the link to the order screen passed the suggested quantity under an unregistered query name, so the order form always opened with a quantity of 1 instead of the shortfall.
* UPC-E was listed as a supported camera format, but the bundled decoder cannot read it, and a UPC-E code that did decode would have been handed to the JAN field in its compressed 8-digit form, matching nothing. It has been removed from the enabled formats and from the documentation. UPC-A, EAN-13 and EAN-8 are unaffected.
* Documentation: corrected several statements in readme.txt that no longer matched the code — the weekly recommendation pass runs three detectors, not four; there is no scheduled manufacturer-list refresh; the campaign digest runs on page view, not on a schedule; offline changes are not queued for later sync; and the lists of what is sent to the web service now name every field, including the material feedback body and the clinic's contact details.

= 1.21.5 =
* Security: all admin-screen GET parameters (display filters, edit links and post-redirect status flags) are now nonce-verified through a single helper; without a valid nonce the screen renders its default state. In the staff PWA, which authenticates with its own JWT session rather than WordPress cookies, the post-redirect banner parameters and the scan hand-off parameters are verified with an equivalent CSRF check before they are read.
* Security: fixed a branch authorization gap on the staff app home screen. The branch given in the query string was used without checking the operator's permission, so a staff or viewer account could read another branch's stock summary by changing it. The branch is now re-authorized with the same check used by the other screens, and the branch switcher is shown only to operators who are allowed to cross branches.
* The one-time dzm_* to dzmaker_* option migration now renames only this plugin's own options from a fixed list of known names instead of scanning wp_options with a LIKE 'dzm_%' pattern, so options belonging to other plugins can never be read, renamed or deleted by it.
* Removed a require of wp-admin/includes/media.php whose functions were never used (wp_handle_sideload and wp_generate_attachment_metadata come from file.php and image.php, which are still loaded and used immediately).
* Uninstall additionally removes two API response cache options.

= 1.21.4 =
* Database: every query now passes its table name to `$wpdb->prepare()` through the `%i` identifier placeholder instead of interpolating it into the SQL string. This covers all 117 call sites across 32 files, including `SHOW COLUMNS`, `ALTER TABLE` and `DROP TABLE`. Because `%i` requires WordPress 6.2, "Requires at least" is raised from 6.0 to 6.2.
* Code quality: removed all 286 file-level `phpcs:disable` comments. The plugin now reports zero errors and zero warnings under PHPCS with the WordPress Coding Standards sniffs that guard database access, request handling, escaping, i18n, prefixing and PHP/WP compatibility (`WordPress.DB`, `WordPress.Security`, `WordPress.WP.*`, `WordPress.NamingConventions.PrefixAllGlobals`, `PHPCompatibilityWP`); the remaining suppressions are all line-scoped and each carries a written justification. The `phpcs.xml.dist` and `composer.json` that reproduce this run are in the plugin's source repository (they are development tooling and are not part of the distributed package).
* Fixed: four call sites queried `wp_dzmaker_corporations` / `wp_dzmaker_suppliers`, tables that do not exist — the real tables are `wp_dzm_*`. As a result the supplier master and seller-linking features always read corporation ID 0, attaching a linked seller from the staff app always failed, and the 1.2.0 database migration silently skipped its supplier seeding and back-fill step.
* Fixed: `public/js/vendor/qrcode-generator.js` had one line altered from the upstream source (the x and y arguments of `renderTo2dContext()` were swapped). Restored to the unmodified upstream 2.0.4 file.
* Fixed: the overstock recommendation summed a column that does not exist in the inventory log table, so it errored on every weekly run and never produced a recommendation. It now works.
* The volume-discount and unused-event recommendations are disabled. They queried supplier columns that were never part of the database schema and nothing ever wrote them, so they have never produced a result; they now no longer log a database error on the weekly cron. Nothing that previously worked has been removed.
* Bundled libraries are now pinned in `package.json`, copied verbatim from upstream, and verifiable against `public/licenses/VENDOR-CHECKSUMS.txt` with `shasum -a 256 -c` from the plugin's root directory.
* Fixed: uninstalling the plugin left two of its twenty database tables behind (`..._dzm_linked_sellers` and `..._dzm_material_feedback`); they were never listed in the uninstaller. All twenty are now removed.
* Admin and PWA view templates now prefix their local variables with `dzmaker_`, which also resolves 28 collisions with WordPress core global variable names.

= 1.21.3 =
* Security: branch-level authorization is now enforced for the My Page inventory view and the PWA stock/order actions. Staff and view-only accounts are restricted to their own assigned branch; only administrators can switch branches. Previously the branch could be changed via a request parameter without a branch-level permission check.
* Privacy/uninstall: the uninstaller now deletes only this plugin's own known options by explicit name (both the current `dzmaker_` and legacy `dzm_` prefixes). It no longer runs a broad `LIKE 'dzm_%'` sweep, so options belonging to other plugins are never touched.
* Hardening: added explicit annotations to the remaining direct database reads (table-name-only queries with no user input; all user-supplied values already go through prepared statements).

= 1.21.2 =
* Compliance: renamed the plugin's internal code prefix from the 3-character "dzm" / "DZM_" to the 4+ character "dzmaker" / "DZMAKER_" across all constants, classes, options, transients, cron hooks, the my-page shortcode, and JavaScript globals, per the WordPress.org unique-prefix requirement. Existing installations are migrated automatically on upgrade (options, scheduled tasks, and the my-page shortcode are carried over; database tables are unchanged, so no data is lost).
* Fix: updated the Author URI to a fast, reliable address.

= 1.21.1 =
* Compliance: moved all inline `<script>` / `<style>` blocks in admin and front-end views into enqueued asset files (wp_enqueue_script / wp_enqueue_style / wp_add_inline_style / wp_localize_script). No functional change.
* Updated the bundled Chart.js library to 4.5.1.

= 1.21.0 =
* New: post requests / suggestions about a registered material to its manufacturer. The 使用材料一覧 (used-materials list) gains a 「投稿」 button per material; the form (with a clear purpose/policy notice, an anonymous toggle, and an "urgent defect" checkbox with an important-notice alert) shows a confirmation screen with 戻る/送信 before sending.
* New: 投稿済一覧 (posted list) screen with progress badges (緊急 / 通知済 / メーカー未開封 / 開封済). "メーカー未開封" means the manufacturer is either not registered in the system or has not opened the post yet.
* The post is stored on the Web side (system of record). Paid manufacturers can view posts limited to their own products on their Web mypage, and receive an email notification linking to the post. Urgent defects additionally notify the system administrator and (when configured) the dental associations.
* Hourly sync pulls the manufacturer "opened" / notification status back into the posted list. DB schema 1.7.0 → 1.8.0 (adds material_feedback).
* Used-materials list: the per-row 操作 / 投稿 buttons are now laid out side by side without arrows (投稿 is navy). Pressing 操作 now smooth-scrolls to a clearly highlighted operation panel (with a "back to list" link and a short description of what you can do there), and a one-line hint above the table explains both buttons.
* New: a Top (機能一覧) landing page placed at the very top of the admin menu. It presents every feature as a styled, categorized card (title + description) that links straight to the feature page. The former home (initial-setup steps + staff-app QR) is renamed to 「はじめに」 and kept directly below Top.

= 1.20.2 =
* Data import (CSV): the JAN code column now accepts GS1 codes too — a GS1 element string (e.g. "(01)…(17)…(10)…" or raw GS1-128/DataMatrix) is parsed to extract the GTIN (matched as a JAN) and, when the expiry / lot columns are blank, the expiry date (AI 17) and lot number (AI 10) are auto-filled.
* Import template: removed the "仕入先URL" column from the sample to keep it simple (the column is still accepted if present).

= 1.20.1 =
* Data import (CSV) screen: the "open the Web version" link now goes directly to the Web matching page (/clinic/import); after logging in there, the clinic lands straight on the import page.

= 1.20.0 =
* New: bulk CSV import for onboarding ("データ取り込み(CSV)" screen). Import past inventory and purchase history at once instead of entering items one by one. Required columns are just product name and quantity; a downloadable template is provided; UTF-8 and Shift_JIS are both accepted; items without a JAN code are supported (matched by in-store code or product name, or created as custom). A preview shows per-row status before committing.
* Two import modes (auto-detected per row): opening balance (current on-hand stock, not counted as an expense) and purchase history (past purchases with date/price, fed into the expense dashboard and price trend, without inflating current stock — no double counting).
* Guidance to optionally match official product names on the Web side first, then import the cleaned CSV. Added an optional onboarding step.

= 1.19.10 =
* i18n maintenance: regenerated the translation template (languages/dentalzaiko-maker.pot) from the current source with wp-cli, and re-synced the bundled Japanese catalog (ja.po/ja.mo). No functional change.

= 1.19.9 =
* Code quality: resolved all WordPress Plugin Check findings in the seller-orders feature files — escaped integer-ID output in the staff/supplier/order PWA views, added a missing "translators:" comment, and annotated known-safe SQL (table-name interpolation, dynamic IN() placeholders) and the dzm_ variable prefix as phpcs-ignored, consistent with the rest of the plugin. No functional or behavioral change.

= 1.19.8 =
* Home screen: the staff app login URL is now shown in a prominent highlighted "重要" box at the bottom, with a QR code so staff can open the app by scanning with their phone, plus a one-tap "copy URL" button. The QR code is generated locally in the browser (bundled MIT qrcode-generator library) — no external request is made.

= 1.19.7 =
* Public-institution report (manual send): when the previous month has no aggregated data (e.g. right after introduction), the screen now shows a clear amber warning explaining nothing was sent and that it is not an error, instead of the cryptic "result: empty" message. Successful sends now also show the target month and item count.

= 1.19.6 =
* Notifications screen: the delivery-channel labels are now shorter — 両方 / メール / 画面 (was 画面＋メール / メールのみ / 画面のみ).
* Notifications screen: the severity badges now use crisper traffic-light colors (緊急 = vivid red / 注意 = vivid amber / 情報 = vivid green) for better at-a-glance visibility.

= 1.19.5 =
* Notifications screen: the severity is now shown as a bold colored badge (緊急 = red / 注意 = amber / 情報 = green) so it stands out at a glance, and the delivery channel is shown in plain Japanese (画面＋メール / メールのみ / 画面のみ) instead of the raw value "both" etc.

= 1.19.4 =
* Email signature (common footer) refreshed: the system name is now a single line ("歯科在庫管理システム DentalZaikoMaker") and the facility contact details (担当 / 住所 / TEL / Email) are indented under the facility name for a cleaner signature.
* Notification emails (expiry warning, low-stock, seller-order status) now open with an addressee line — the recipient's name when it can be resolved (staff / WordPress user), otherwise a generic "在庫管理ご担当者 様".
* Low-stock email now includes a body (material name / remaining quantity / threshold) instead of subject-only; expiry email body now also states the material name; seller-order status email adds a "check your order history" guidance line; internal order-request notification now addresses each administrator.

= 1.19.3 =
* Public-institution report screen: the participation request is now shown as a large warning banner at the very top of the page, and it is hidden once the clinic has opted in.

= 1.19.2 =
* Fix: the cost-saving recommendations screen was always empty — its SQL selected a non-existent `branch_name` column from the branches table (column is `name`), so the query silently failed. Also fixed the same wrong-column bug in the accounting CSV exports (freee / MoneyForward / transaction list), which were producing empty files.
* The recommendations screen now shows the "detection batch" result message after running it, and avoids a wpdb::prepare placeholder notice on the unfiltered ("all") view.

= 1.19.1 =
* Public-institution report screen renamed to "公的機関レポート(価格状況の月次通知)".
* Added an optional onboarding step ("月次価格レポートに参加する(任意)") and an encouragement note on the report screen to make participation easy to discover. The monthly report remains strictly opt-in (default OFF) per WordPress.org guideline 7; the step completes once the admin makes a choice (join or decline), so it never auto-enables data sending.

= 1.19.0 =
* Inventory list usability: both the admin "Inventory" screen and the staff My Page stock list now have a search box (matches material name, JAN, in-store code, or maker) and a sort dropdown (earliest expiry first, lowest stock, reorder-needed first, name, most recently received). The admin screen filters/sorts server-side via the existing branch form; the My Page list does it client-side (no reload), combined with the existing category chips. No database changes.
* Admin menu: "材料" submenu renamed to "材料一覧". Seller-link screen now explains that seller codes are issued from the partner portal (healthbanks.org/dzm).
* Fix: the expense dashboard's "monthly trend" chart was always empty because its SQL aliased a reserved word (`year_month`) without quoting, so the query silently failed on MySQL/MariaDB. Also fixed the branch labels on the expense dashboard and branch-comparison charts (were reading a non-existent `branch_name` column instead of `name`).

= 1.18.0 =
* Notification emails now carry a consistent signature footer — the facility name (clinic or laboratory) followed by its contact details (representative, address, phone, email; only the fields that are filled in) — and a short purpose line at the top tailored to each notification type. Plain-text only; login links and secrets are never embedded.
* Web seller orders: when a linked seller updates an order's status (order confirmed / shipped / declined), the staff member who requested the order is now notified by email in addition to the in-app notice.
* Admin "Notifications" screen: the body column is now left-aligned (fixed a white-space:pre-wrap indentation glitch that pushed the text to the right), and the "type" column shows readable Japanese labels (e.g. "使用期限", "在庫しきい値", "受注確認(販売店)") instead of raw codes — the raw code is still available on hover.

= 1.17.0 =
* Sellers (販売店), Phase 5: order status now syncs back automatically. An hourly cron polls each linked seller for status changes (order confirmed / shipped / declined), updates the clinic's order history, and notifies the staff member who requested the order (in-app).

= 1.16.0 =
* Sellers (販売店), Phase 4: clinics can now send approved orders to a linked seller through the web order API instead of email. The order-management screen gains a "send web order" action for sellers connected by code. Database upgrade to 1.7.0 (seller order tracking columns: web order no. and status). Applied automatically on update; no data loss.

= 1.15.0 =
* Sellers (販売店), Phase 3: a clinic can link a dealer by seller code. A new "seller linkage" admin screen calls the web API to connect; once linked, the seller appears as a selectable supplier on the order screens. Database upgrade to 1.6.0 (local seller-linkage table). Applied automatically on update; no data loss.

= 1.14.0 =
* Ordering: a web-recommended company that is not yet registered as a supplier can now be added as a supplier in one click directly from the order screen, and the action-select screen shows a supplier card. This makes it quicker to turn a recommendation into an actual order.

= 1.13.0 =
* Inventory list (staff mypage): added a product major-category filter. The in-house stock list now shows a category chip bar (orthodontics, implant, crown/prosthetic, cutting tools, etc.) and a small category badge on each item, so staff can quickly narrow a long inventory by category. Filtering is client-side and works offline.
* Medicines are sub-labelled by the JDTA dental medicine classification (local anesthetic, antibiotic, caries-prevention, root-canal, etc.) under the "dental medicine" category.
* Category is derived locally from the cached generic name, so it works even before the next server sync.

= 1.12.0 =
* Product major-category (大分類): materials now carry an 18-way product category (orthodontics, implant, crown/prosthetic, etc.) derived on the web side from the official generic name. The plugin caches it per material (new local_materials.major_category column) on registration and during sync, enabling category-grouped browsing/filtering of in-house inventory.
* Material sync now also refreshes the cached major-category from the server.
* Database upgrade to 1.5.0 (adds local_materials.major_category). Applied automatically on update; no data loss.

= 1.11.0 =
* Barcode scanner (staff PWA): packaging GTIN-14 support. The scanner now passes the raw GS1 GTIN-14 (e.g. a sales-pack barcode with package indicator 1) instead of collapsing it to a 13-digit JAN, so packaging-level codes that have no matching JAN now resolve correctly.
* Material matching: local lookup is now GTIN-first. The plugin remembers the actual scanned GTIN per material (new "learned GTIN" column), so re-scanning the same box hits locally without a server round-trip. Falls back to JAN normalization (indicator 0 → 13-digit JAN) and then the Web master lookup (now queried by gtin).
* Database upgrade to 1.4.0 (adds local_materials.gtin). Applied automatically on update; no data loss.

= 1.10.0 =
* Barcode scanner (staff PWA): GS1/UDI support. Scanning a medical-supply GS1 code (1D GS1-128 / GS1 DataBar, or 2D DataMatrix/QR) now auto-fills the lot number (AI 10) and expiry date (AI 17) on the stock-in form — no more manual typing. JAN-only products are unaffected (lot/expiry remain manual as before).
* Scanner now also reads 1D GS1 barcodes (GS1-128 / GS1 DataBar Expanded), in addition to the previously supported JAN/EAN and 2D GS1 DataMatrix/QR.
* Expiry AI(17) YYMMDD is converted to a real date (DD=00 → end of month); lot AI(10) is read up to 32 characters. Auto-filled values are shown with a "scanned" badge and remain editable.

= 1.9.0 =
* Events (特売): switched the staff mypage event matching from per-JAN API calls to a single "active events digest" fetched with conditional GET (ETag/304) and matched locally against in-house inventory. This keeps central server load independent of the number of clinics and inventory size. Order-time details still use the per-JAN recommendations API.
* Mypage: the 販促イベント (sale) list now also shows the discount rate fallback and the partner company name in the alert.
* Requires the Web side (healthbanks.org/dzm) to expose /plugin/events/active-digest; the plugin falls back gracefully (cached/empty) when unavailable.

= 1.8.4 =
* Terminology: unified the supplier label to "仕入先" across the whole plugin (staff PWA, admin material/inventory/order screens, and shared messages), replacing the older mixed "購入先" wording. Display text only — no change to data, validation, or order processing.

= 1.8.3 =
* Staff My Page: fixed the "Staff info" panel not appearing on iPhone/iOS Safari — tapping the tile now reliably opens the panel and scrolls it into view (the previous CSS-only reveal did not scroll to the panel on iOS).

= 1.8.2 =
* Staff My Page: tapping the "Staff info" tile now opens an info panel showing the signed-in staff's name, role, branch, login method, and email, with a logout button (previously it only scrolled to the top with no feedback).

= 1.8.1 =
* Staff app: the stock list is now its own full screen, and a new admin-only staff registration screen was added (register/edit staff with role, branch, and PIN; reuses the existing invite-email/PIN flow). Staff information can only be changed by administrators (with an on-screen note).
* Staff app: added a consistent quick-navigation header (Home / Stock-in / Stock list) to the supplier-registration, stock-in (scan), stock-list, and staff-registration screens.

= 1.8.0 =
* Suppliers can now store an email address and a contact-person name (staff PWA registration form and the admin supplier editor).
* The staff PWA supplier screen now lists the registered suppliers below the registration form, each expandable to edit its details (name, kana, URL, email, contact person, notes, active) inline.
* Order emails: from the admin "Order requests" screen, approved orders that have not yet been emailed are grouped per supplier (for suppliers that have a registered email) and can be sent as a single combined order email per supplier. The email lists each product and quantity and is sent with the clinic's contact info as the reply-to. Suppliers without an email continue to use the existing EC redirect link.
* DB schema 1.3.0: adds suppliers.email / suppliers.contact_person and order_history.order_email_sent_at / order_email_to / order_email_batch_id (auto-migrated on update).

= 1.7.1 =
* Staff My Page: the "Log in with email and PIN" control on the login screen is now a button matching the WordPress login button above it, so it is easier to recognize as tappable.
* Staff My Page: the summary chips (reorder / expiring soon / on-sale) now open a dedicated full-screen filtered list for that category (with a "back to My Page" link) instead of a static badge, including a new "Expiring soon (use-by)" list.
* Staff PWA: the "back to action select" link on the stock-in/out and order screens is now a button, consistent with the "scan another code" button.
* Staff PWA: when a material has no stock (lot) at the branch, the use/dispose screen no longer shows an unusable empty lot selector; it now shows a clear "no stock" message with a shortcut to record a stock-in. When only one lot exists it is pre-selected.

= 1.7.0 =
* Staff PWA scanner now reads 2D codes (GS1 DataMatrix and QR) in addition to JAN/EAN barcodes, so medical-supply packaging that carries a GS1 DataMatrix can be scanned. GTIN-14 values are converted to the 13-digit JAN to match existing inventory.
* Replaced the barcode engine for reliable DataMatrix/QR recognition on iOS Safari, where the native BarcodeDetector API is unavailable. (Superseded in 1.21.6, which decodes with a bundled pure-JavaScript library.)
* Scanner UX: the on-screen frame now matches the actual scan region, the guidance asks the user to hold the camera slightly farther for focus, and an optional flashlight (torch) button appears on devices that support it.
* Fixed an issue where re-opening the scanner from "scan again" could show the previously scanned value (iOS Safari back-forward cache); the scanner now resets on each view. Added a "scan another code" link to the material detail screen.
* Product images are now downscaled to fit within 240x240 pixels and re-encoded on upload, reducing storage on the server. Material images in the stock lists (staff page and admin) can be tapped to view an enlarged version with a close button.

= 1.6.0 =
* Added a login-gated, tablet/phone-optimized staff inventory page via the `[dzm_mypage]` shortcode (a "My Page" hub): camera JAN scanning, product images, an enriched stock list with reorder and sale-event alerts, supplier registration, and quick ordering.
* Dual authentication for the staff page and operations: WordPress login (mapped to the staff role and branch) or the existing email + PIN session. Viewer-only accounts cannot perform operations.
* Administrators can place orders from the staff page without a separate approval step (auto-approve); staff orders still go through clinic-director approval.
* Admin: made the "apply / get a connection key" link a prominent button on the connection settings screen, and added an input helper on the Add Location screen to copy the main clinic's registered details.

= 1.5.0 =
* Improved the connection-key onboarding flow: a single primary path for the connection key, a connection-status card (unconfigured / saved / connected / error) with masked token display, inline paste-mistake detection, separation of key reveal vs. reissue, and reference numbers on authentication errors.

= 1.4.0 =
* Added an OPTIONAL monthly statistics report (opt-in, OFF by default) that sends aggregated per material/category monthly price statistics for the previous month to the companion web service, to help public bodies (dental associations, the Ministry of Health, Labour and Welfare) understand material price trends. Raw orders, patient, and clinical data are never sent.
* New "Public report" settings screen: explicit opt-in toggle, sender anonymization toggle (anonymous by default), a disclosure of exactly what is sent, and a required acknowledgement. Sending can be turned off at any time.
* The report runs on a monthly WP-Cron schedule only after opt-in; when not opted in, no data is sent.
* Added a "Price trend (own clinic)" admin screen that visualizes the clinic's own monthly average unit price by category (Chart.js). This reads local data only and never sends anything externally.
* Updated the readme disclosure and privacy policy to describe the optional outbound monthly report.

= 1.3.0 =
* Added a corporation-wide supplier master so administrators can pre-register suppliers from a dedicated admin page; staff can then pick from the list on the PWA material registration screen instead of typing supplier names every time.
* Inline-create flow on the PWA: choosing "+ 新しい仕入先を追加" lets staff create a master record on the fly without leaving the registration form.
* Added an event alert on the PWA material registration screen: when the master DB returns one or more active events for the same product, a deep-navy info card surfaces the offers and links to the web service event detail page.
* DB migration: new wp_dzm_suppliers table (corporation-scoped, unique by name) plus a supplier_id foreign key on wp_dzm_material_suppliers. Existing material_suppliers.company_name_cache values are auto-aggregated into the master and back-filled on upgrade.

= 1.2.1 =
* Added a global navigation drawer (hamburger menu) to the staff PWA so users can jump between Home, Scan, New material, Inventory list, and Logout from any sub-page.
* Improved Lighthouse Accessibility score to 100 on PWA pages: associated `<select>` elements with `<label for>`, fixed heading order (`<h3>` → `<h2>`), added `role="img"` + `aria-label` to all Chart.js canvases, and grouped radio sets with `<fieldset><legend>`.
* Material edit preview image now has a descriptive alt attribute instead of an empty one.

= 1.2.0 =
* UX/UI overhaul: deep-navy primary (#1e3a8a, AAA 8.59:1) + red CTA accent applied to all 26 admin and PWA views, with Nielsen 10 heuristics and consistent BEM `.dzm-` prefix.
* Hardened the opt-in consent flow: pre-consent error notice in red, blinking call-to-action, and stronger guard rails (`dzm_consent_required` is checked at JWT acquisition and at every API call).
* Image upload now compresses to display-required size (long edge 1600px, JPEG 0.85) on the client side before posting, both in the admin material editor and the staff PWA.
* WP.org Plugin Check fully clean (0 errors / 0 warnings) — the plugin is now ready for the WordPress.org submission.

= 1.1.0 =
* Added product image upload from the staff PWA (camera/library) and image display across PWA and admin screens.
* New PWA flow: register a brand new material directly from a smartphone, including a product photo, in a single screen.
* DB migration: added local_materials.image_attachment_id (links to the WordPress Media Library).

= 1.0.1 =
* Added detection of JDTA master changes (discontinuation warnings and name change notifications).
* Order forms can now choose between the latest master name and the locally saved name; historical records keep the original name.
* Added a "Refresh from server" button on the material detail screen, shown only when the master has changed.
* DB migration: added name snapshot columns to local_materials, order_requests, and order_history.

= 1.0.0 =
* Initial release.
