Skip to content

bug2#19

Open
AbdullahBakir97 wants to merge 93 commits into
mainfrom
test/quality-bots
Open

bug2#19
AbdullahBakir97 wants to merge 93 commits into
mainfrom
test/quality-bots

Conversation

@AbdullahBakir97

Copy link
Copy Markdown
Owner

No description provided.

Removed iOS-inspired dark UI feature from README.
Updated the build instructions in README.md to simplify the process and remove unnecessary steps.
…rix colors

- Professional sidebar navigation with collapsible categories
- 4 theme presets (Pro Dark, Pro Light, Classic Dark, Classic Light)
- Header bar with search, notifications, animated dark/light toggle
- Custom footer bar replacing QStatusBar
- Quick Scan with TAKEOUT/INSERT modes, pending list, batch confirm
- Command barcodes (TAKEOUT/INSERT/CONFIRM) configurable per shop
- Global barcode capture — scanner works from any page without clicking
- Matrix table Excel-like color banding per part type
- Right-click context menu on matrix cells (stock ops + assign barcode)
- Barcode assignment dialog for matrix items
- Admin Scan Settings tab for command barcode configuration
- DB V5 migration for scan command defaults
- Monospace fonts for stock numbers, barcodes, timestamps
- 48px table rows, smooth pixel scrolling
- Modernized dialogs with close buttons and consistent spacing
- Order field (was Inventur) for delivery tracking
- i18n updates (EN/DE/AR) for all new features
…erhaul

Barcode Generator:
- PDF barcode sheet generation with Code39/Code128 support
- Brand-separated pages (Apple, Samsung, Xiaomi on separate pages)
- Part-type grouped pages with headers (e.g., "Apple - (JK) incell FHD")
- Vertical command barcodes (ADD/DEL/OK) spanning row blocks
- Scope selector: generate by category, model, or part type
- Auto-naming convention: BRAND-MODEL-PARTTYPE (e.g., A-14P-JKIF)
- Assign & Save barcodes directly to inventory items
- PDF preview with page navigation, export, and print
- Sort by DB sort_order (X, XS, XR, 11, 12... not alphabetical)

Quick Scan System:
- TAKEOUT/INSERT modes with command barcodes
- Pending list with batch confirm (nothing commits until CONFIRM scan)
- Global barcode capture — scanner works from any page without clicking
- Remove items from pending list with × button
- Session management with cancel/confirm

Demo Data:
- Samsung A-series with 4G/5G variants and model codes (A045F, A136B, etc.)
- Xiaomi/Redmi models (A5, 13C-15C, Note 11-14 Pro+)
- Apple 17 series added
- Brand-specific display types (Apple: 5 types, Samsung/Xiaomi: 2 types)
- Model-specific display exclusions (e.g., no JK for iPhone 17)

Professional UI:
- Sidebar: scrollable, collapsible categories, hideable (hamburger + Ctrl+B)
- Header: notification bell with badge, animated theme toggle, language switcher
- Footer: custom status bar with version and sync indicator
- Layout stability: QStackedWidget size policy fixes, no more window resizing
- Matrix: Excel-like color banding with custom delegate (bypasses QSS)
- Admin: icon picker grid for categories, barcode editor in part types
- Close buttons: × character with dedicated QSS style
- ComboBox/SpinBox: native Qt arrows (removed broken CSS triangles)
- Icon recoloring for dark mode visibility
- Responsive layout with proper size policies throughout

Database:
- V5 migration for scan command barcode defaults
- Brand-aware ensure_matrix_entries (display exclusions)
- Bulk barcode update, get_by_part_type, get_items_without_barcode

Build:
- PyInstaller spec updated with PIL, barcode, fpdf2 collection
- Force-collect PIL .pyd files for Python 3.14 compatibility
- Icon converted from PNG to ICO
Updated README to reflect version 2.0.0 changes, including new features, improved descriptions, and added sections.
…orting

Color System:
- part_type_colors table (V6 migration) for per-part-type color variants
- Two-step Quick Scan: scan model barcode → scan color barcode (CLR-BLACK, etc.)
- ScanSessionService WAITING_COLOR state with auto-resolve
- Color management UI in Admin → Part Types (visual color picker, multi-select)
- Color barcodes configurable in Admin → Scan Settings
- Matrix shows colored sub-rows with actual color indicators (●)
- Colorless parent items for barcodes, colored items for stock tracking
- Color barcodes PDF page with colored backgrounds

Barcode Generator Fixes:
- Fixed F prefix: brand code uses first letter only (A=Apple, S=Samsung, X=Xiaomi)
- Fixed scanner ß mapping: DB stores fSßA04ßSMO (scanner output), PDF prints S-A04-SMO
- _to_code39() strips f prefix and converts ß→- for barcode images
- _barcode_for_db() adds f prefix and converts -→ß for DB storage
- Fixed abbreviations: A04, A04E, A04S (was all just 'A'), Pro→P, max→M, Plus→PL
- Colored items skipped in barcode generation (use 2-step scan instead)
- Command barcodes use correct Code39 text matching old printed sheets

Demo Data:
- Samsung A-series with 4G/5G variants and model codes (34 models)
- Xiaomi/Redmi 16 models added
- Apple 17 series added (28 total)
- Brand-specific display exclusions (JK not for 17, DD Diagnose exclusions)
- Display types: Apple 5 types, Samsung/Xiaomi 2 types (ORG Service Pack, OLED)

Matrix & Sorting:
- Natural sort everywhere: X→XS→XR→11→12...17, A04→A05→A12→A22→A56, S10→S24
- Brand filter hides irrelevant part types (Samsung shows only OLED + ORG SP)
- Color sub-rows with colored text matching actual color
- Colorless parents hidden from matrix when colors exist

UI Polish:
- All admin panels scrollable (Shop, Scan, Categories, Models, Part Types)
- QuickScan tab scrollable
- Admin dialog resized to 800×650
- All hardcoded strings translated (EN/DE/AR) — 15 new i18n keys
…ntroller architecture

72 files changed, 32 155 insertions, 10 760 deletions
Previous release: v2.2 (schema V5, products/stock_entries legacy, monolithic main_window)

════════════════════════════════════════════════════════════════════════════
DATABASE — Schema evolved from V5 → V12 (7 new migration paths)
════════════════════════════════════════════════════════════════════════════

V6  part_type_colors table; rebuild inventory_items with UNIQUE(model_id,
    part_type_id, color) constraint; drop legacy products, stock_entries,
    product_transactions, stock_transactions tables (Phase C complete)

V7  image_path column on inventory_items for product photo storage

V8  expiry_date + warranty_date columns on inventory_items; create
    locations table; location_stock join table; stock_transfers table;
    seed default "Main" location; backfill location_stock from item.stock

V9  sales + sale_items tables (POS foundation)

V10 customers table (name, phone, email, address); customer_id FK on sales;
    idx_customers_name + idx_customers_phone

V11 purchase_orders (PO lifecycle DRAFT→SENT→PARTIAL→RECEIVED);
    purchase_order_lines (line items with received_qty tracking);
    returns table (action: RESTOCK/WRITE_OFF, refund_amount)

V12 suppliers enhanced (contact_name, phone, email, rating, updated_at);
    supplier_items (cost_price, lead_days, supplier sku, UNIQUE);
    inventory_audits (stocktake cycles, status, started/completed_at);
    audit_lines (system_qty, counted_qty, difference per item per audit);
    price_lists (active/draft/archived status);
    price_list_items (per-item override prices, UNIQUE constraint)

Current _SCHEMA_VERSION = "12"

Full table inventory (22 tables):
  app_config, categories, part_types, phone_models, part_type_colors,
  inventory_items, inventory_transactions,
  suppliers, supplier_items,
  locations, location_stock, stock_transfers,
  customers,
  sales, sale_items,
  purchase_orders, purchase_order_lines,
  returns,
  inventory_audits, audit_lines,
  price_lists, price_list_items

════════════════════════════════════════════════════════════════════════════
ARCHITECTURE — Zero-freeze async engine + controller decomposition
════════════════════════════════════════════════════════════════════════════

app/ui/workers/worker_pool.py (NEW)
  • QThreadPool-backed POOL singleton with keyed task cancellation
  • _Signals(QObject) carrier — QRunnable cannot host Qt signals directly
  • threading.Event cancellation flags discard stale in-flight results
  • submit(key, fn, on_result, on_error) — named task slots, cancel on resubmit
  • submit_debounced(key, fn, on_result, delay_ms) — QTimer-gated debounce
  • cancel(key) / _invalidate(key) / _cleanup(key, ev) full lifecycle

app/ui/workers/data_worker.py (NEW)
  • DataWorker(QRunnable) generic background fetch with _Signals carrier
  • Used by StartupController for two-phase async startup

app/ui/workers/update_worker.py (NEW)
  • UpdateWorker(QRunnable) for manifest-based version checks off main thread

app/ui/controllers/ (NEW directory — 7 controllers)

  nav_controller.py (137 lines)
    Registry-based navigation: register(key, page_index, on_activate)
    go(key) switches QStackedWidget + fires on_activate callback
    toggle_sidebar() owns visibility + ☰/× glyph
    rebuild_matrix_tabs() / retranslate_matrix_tabs()
    navigated = pyqtSignal(str); current: str property

  startup_controller.py (129 lines)
    Two-phase async startup: begin() → singleShot(0, _load_summary)
    Phase 1 DataWorker: summary KPIs → dashboard + analytics + alerts paint
    Phase 2 singleShot(0, _load_inventory) after analytics paints
    ready = pyqtSignal(); _drop(worker) safe keep-alive removal

  update_controller.py
    Manifest-based update check via UpdateWorker; badge wiring via signal

  alert_controller.py
    StockAlertCounts refresh via POOL; badge + panel wiring

  stock_ops.py
    Fixed _check_alerts → _alert_ctrl.refresh() (×2)
    Fixed _nav_to("nav_transactions") → _nav_ctrl.go("nav_transactions")

  bulk_ops.py
    Removed 2 redundant _check_alerts() calls (_refresh_all covers alerts)

  inventory_ops.py
    Inventory-specific operations extracted from main_window

app/ui/main_window.py (2 263 → 572 lines)
  All DB calls removed from main thread; every fetch via POOL
  _refresh_all() fires 6 parallel POOL jobs simultaneously
  _deferred_retranslate_refresh() routes analytics + transactions via POOL
  _on_summary_ready() syncs _inv_page._cached_count
  NavController instantiated in _build_ui() after stack is complete
  Module-level singletons: _cat_repo, _txn_repo, _item_repo, _stock_svc, _alert_svc

════════════════════════════════════════════════════════════════════════════
CORE MODULES
════════════════════════════════════════════════════════════════════════════

theme.py
  set_theme() defers setStyleSheet via QTimer.singleShot(0) — kills freeze
  Internal state (tokens, is_dark) updates immediately for correct animation
  _apply_ss(root, ss) with RuntimeError guard for destroyed widgets
  Pro Dark glass search bar: transparent bg, rgba(255,255,255,45) rim
  Pro border radii: br_card/btn/input/table/tab = 6/4/4/6/6px

i18n.py
  Merge conflict resolved; translations expanded to cover all new pages,
  dialogs, admin panels, controllers, and components (EN/DE/AR)

colors.py (NEW)
  PALETTE: 24-color OrderedDict Red → White
  hex_for(name), is_light(hex_str), all_names()
  Used by ColorPickerDialog, ProductDetailBar, color swatch rendering

version.py (NEW)
  APP_VERSION = "2.0.0" — single source of truth
  UPDATE_MANIFEST_URL for remote manifest check

health.py (NEW)
  DB health queries for AboutPanel system info display

logger.py (NEW)
  Structured logging with rotation; level from app_config
  get_logger(__name__) used across all modules

database.py (1 112 lines)
  Full V1→V12 migration chain as documented above
  _DDL defines all 22 tables with indexes in one idempotent script
  _ensure_all_entries() populates matrix (model × part_type) gaps
  load_demo_data() seeds Galaxy@Phone demo with all 3 languages

════════════════════════════════════════════════════════════════════════════
MODELS — 9 new domain models (13 total, product.py deleted)
════════════════════════════════════════════════════════════════════════════

audit.py          AuditCycle(id, name, status, notes, started_at, completed_at)
                  AuditLine(item_id, system_qty, counted_qty, difference)
                  AuditStatus enum: IN_PROGRESS / COMPLETED / CANCELLED

customer.py       Customer(name, phone, email, address, notes, is_active)

location.py       Location(name, description, is_default, is_active)
                  LocationStock(item_id, location_id, quantity)

price_list.py     PriceList(name, description, is_active, created_at)
                  PriceListItem(price_list_id, item_id, price)
                  PriceListStatus enum

purchase_order.py PurchaseOrder(po_number, supplier_id, status, notes)
                  POLineItem(po_id, item_id, quantity, cost_price, received_qty)
                  POStatus enum: DRAFT/SENT/PARTIAL/RECEIVED

return_item.py    Return(sale_id, item_id, quantity, reason, action, refund_amount)
                  ReturnAction enum: RESTOCK / WRITE_OFF

sale.py           Sale(customer_name, customer_id, total_amount, discount, note)
                  SaleItem(sale_id, item_id, quantity, unit_price, cost_price)

supplier.py       Supplier(name, contact_name, phone, email, address, rating)

item.py           Added: image_path, expiry_date, warranty_date fields

product.py        DELETED — Phase C migration complete

════════════════════════════════════════════════════════════════════════════
REPOSITORIES — 9 new, 4 updated, 1 deleted (13 total)
════════════════════════════════════════════════════════════════════════════

NEW:
  audit_repo.py          CRUD for inventory_audits + audit_lines
                         get_active(), get_completed(), get_lines(audit_id)
  customer_repo.py       Customer CRUD; get_by_phone(); purchase_history(id)
  location_repo.py       Location CRUD; get_stock(item_id); occupancy query
  price_list_repo.py     PriceList CRUD; get_active(); get_items(list_id)
  purchase_order_repo.py PO CRUD + lines; get_by_status(); receive_line()
  return_repo.py         Return CRUD; get_by_sale(); get_by_item()
  sale_repo.py           Sale CRUD; get_items(sale_id); revenue_by_date()
                         daily_summary(); top_selling_items()
  supplier_repo.py       Supplier CRUD; get_items(supplier_id); link/unlink item
  customer_repo.py       Customer CRUD with phone index search

UPDATED:
  item_repo.py       Added supplier_id, location_id, image_path, expiry/warranty
                     filters; get_low_stock(); get_expiring(days)
  transaction_repo.py  get_filtered(filters) → paginated; count_filtered();
                       get_summary_stats() for transaction strip
  category_repo.py   Updated for V6+ schema
  model_repo.py      Updated for V6+ schema

DELETED:
  product_repo.py    Removed — legacy products table dropped in V6

════════════════════════════════════════════════════════════════════════════
SERVICES — 14 new, 5 updated (21 total)
════════════════════════════════════════════════════════════════════════════

NEW:
  audit_service.py         create_cycle(); record_count(audit_id, item_id, qty)
                           finalize(audit_id) — calculates variances, commits
  backup_scheduler.py      BackupScheduler(QObject) 5-min QTimer; off-thread
  customer_service.py      Customer CRUD + purchase_history aggregation
  image_service.py         Product image import; resize via Pillow; path cache
  location_service.py      Location CRUD; transfer_stock(item, from, to, qty)
  price_list_service.py    apply_price_list(list_id) bulk-updates inventory
                           sell_price from price_list_items
  purchase_order_service.py create_po(); send(); receive_line(line_id, qty)
                            → auto stock-in via StockService
  receipt_service.py       PDF receipt generation via fpdf2; logo embed
  return_service.py        process_return() → reverse transaction +
                           refund record; RESTOCK vs WRITE_OFF dispatch
  sale_service.py          checkout(cart) → deduct stock per item →
                           create Sale + SaleItems; discount handling
  supplier_service.py      Supplier CRUD; link_item / unlink_item
  undo_service.py          undo_transaction(txn_id) — reverses last
                           IN/OUT/ADJUST per item with inverse operation
  update_service.py        Manifest-based version check; UpdateManifest
                           dataclass; download(url, progress_cb)
  export_service.py        CSV/Excel/JSON export for all entity types
  import_service.py        CSV import with row-level validation + error report

UPDATED:
  alert_service.py    StockAlertCounts(low, out, total, has_out) dataclass
  backup_service.py   auto_backup(retain, backup_dir) with retention purge
  stock_service.py    undo support; supplier_id/location_id propagation
  report_service.py   PDF reports via fpdf2 (transactions, inventory summary)
  barcode_gen_service.py  Updated for V6+ unified inventory schema

════════════════════════════════════════════════════════════════════════════
UI COMPONENTS — 22 components in app/ui/components/
════════════════════════════════════════════════════════════════════════════

footer_bar.py        FooterBar(QFrame) level-aware status + filter hint
header_bar.py        Glass search bar; left-fixed at x=224 aligning to content
theme_toggle.py      ThemeToggle animated sun/moon; freeze fixed via singleShot
language_switcher.py LanguageSwitcher animated dropdown; _LangRow hover paint;
                     _TriggerButton; live language switch with RTL support
update_banner.py     UpdateBanner slide via QPropertyAnimation on maximumHeight;
                     Install / Skip / Remind Later; QProgressBar for download
notification_panel.py NotificationPanel: alert counts + update badge +
                      low-stock quick list; view_alerts_requested signal
product_detail_bar.py ProductDetailBar: identity, stock, price, min/diff;
                       sparkline trend; quick IN/OUT/ADJUST/Edit/Delete buttons;
                       color swatch via is_light()
product_detail.py    ProductDetail compact side panel
product_table.py     ProductTable(QTableWidget) responsive column hiding
transaction_table.py TransactionTable with copy-row context menu
responsive_table.py  _TableResizer(QObject) + make_table_responsive() utility
toast.py             ToastManager floating non-blocking notifications with stack
loading_overlay.py   LoadingOverlay spinner for async page loads
splash_screen.py     SplashScreen with progress bar and branding
sidebar.py           Sidebar extracted from main_window; nav button registry
charts.py            Reusable chart widgets for analytics / dashboard
collapsible_section.py CollapsibleSection for admin panels
empty_state.py       EmptyState icon + title + subtitle widget
mini_txn_list.py     MiniTxnList compact transaction list for detail panels
barcode_line_edit.py BarcodeLineEdit with scan-signal debounce
filter_bar.py        FilterBar reusable filter strip component
dashboard_widget.py  SummaryCard setUpdatesEnabled batching; child selector QSS
matrix_widget.py     Updated for V6+ color constraint

════════════════════════════════════════════════════════════════════════════
UI PAGES — 11 pages in app/ui/pages/
════════════════════════════════════════════════════════════════════════════

analytics_page.py   _fetch_all_data() single BG job: get_summary() once (was 3×)
                    _apply_all_data() dispatches 8 apply methods on main thread
                    retranslate() no longer triggers 8 sync DB queries

transactions_page.py fetch_filtered() → {rows, total, stats} off main thread
                     load_results(data) main-thread only
                     _apply_filters() → POOL.submit_debounced("txn_filter")
                     Summary strip: total, IN, OUT, net with color labels
                     Load More pagination with offset tracking

inventory_page.py   fetch_filtered(filters) BG-safe; load_items(items) main-thread
                    _cached_count for dashboard KPI sync

audit_page.py (677 lines)
                    AuditListView: cycle list, status filters All/Active/Completed
                    AuditDetailView: item-by-item count entry, variance column
                    NewAuditDialog: name + description
                    KPI cards: total, active, completed, avg variance %

price_lists_page.py (673 lines)
                    _PriceListsOverviewView: KPI cards, All/Active/Draft/Archived tabs
                    _PriceListDetailView: items table, bulk % markup, apply to inventory
                    PriceListService.apply_price_list(list_id) integration

sales_page.py (600 lines)
                    SalesPage: KPI cards + sales history with date filter
                    POSDialog: product picker, cart, qty adjust, running total, checkout
                    ReceiptService → PDF receipt on checkout

purchase_orders_page.py (563 lines)
                    Full PO lifecycle: DRAFT → SENT → PARTIAL → RECEIVED
                    _PODialog: supplier select + line items
                    _AddItemDialog: add inventory items to PO lines
                    Receive PO → auto stock-in via StockService
                    KPI cards: total, draft, sent, received

returns_page.py (339 lines)
                    _ReturnDialog: item select, reason, refund amount
                    ReturnService: reverse txn + refund record
                    KPI cards: total, approved, written-off, refunded

suppliers_page.py (536 lines)
                    _SupplierDialog: supplier CRUD with contact + rating
                    _SupplierItemsDialog: link/unlink inventory items
                    KPI cards + search

reports_page.py     ReportsPage: type picker → PDF via ReportService
barcode_gen_page.py Updated for V6+ unified schema

════════════════════════════════════════════════════════════════════════════
UI DIALOGS
════════════════════════════════════════════════════════════════════════════

product_dialogs.py (expanded)
  ModernDialog(QDialog) — themed header + action footer
  FormField(QWidget) — label + input with validation state
  QuantitySpin(QWidget) — +/- spin with keyboard input
  ColorPickerDialog — 24-color grid from colors.PALETTE
  ColorButton(QPushButton) — live color swatch icon
  ProductDialog — full add/edit: barcode, color, min stock, pricing, image
  StockOpDialog — IN/OUT/ADJUST with QuantitySpin + reason
  LowStockDialog — alert list with product_selected pyqtSignal

dialog_base.py (NEW)    DialogBase(QDialog) shared modal foundation
bulk_price_dialog.py (NEW) Bulk % price change across selection
price_list_dialogs.py (NEW) PriceListDialog + PriceListItemDialog
help_dialog.py (NEW)    Tabbed documentation dialog

Admin dialog expanded to 14 panels:
  admin_dialog.py (274 lines) + preview_banner_requested pyqtSignal

  Existing panels updated:
    shop_settings_panel, categories_panel, part_types_panel,
    models_panel, scan_settings_panel, color_picker_widget

  New panels:
    about_panel.py (547 lines)
      System info: schema V12, DB size, OS, Python, PyQt6 build
      UpdateBanner live-preview trigger for testing
    backup_panel.py
      Manual backup trigger + retention-managed backup list
    db_tools_panel.py
      VACUUM, integrity check, schema version display
    import_export_panel.py
      CSV/JSON import + export per entity type
    locations_panel.py (362 lines)
      _LocationDialog CRUD (name, description, default flag)
      KPI card, searchable table
    customers_panel.py
      Customer CRUD with purchase history
    suppliers_panel.py
      Supplier CRUD in admin context

════════════════════════════════════════════════════════════════════════════
UI TABS
════════════════════════════════════════════════════════════════════════════

quick_scan_tab.py (NEW) QuickScanTab: barcode scan → stock-op dispatch
stock_ops_tab.py (NEW)  StockOpsTab extracted from main_window
base_tab.py             Updated for V6+ schema + new component structure
matrix_tab.py           Updated for color constraint + part_type_colors

════════════════════════════════════════════════════════════════════════════
TESTS — full pytest suite (app/tests/, 30+ test modules)
════════════════════════════════════════════════════════════════════════════

Infrastructure:
  conftest.py        in-memory SQLite fixtures, full schema, seeded data
  __init__.py        test package helpers
  run_tests.py       CLI runner with coverage report

Repository tests:
  test_item_repo, test_transaction_repo, test_category_repo, test_model_repo
  test_audit_repo, test_customer_repo, test_location_repo
  test_price_list_repo, test_purchase_order, test_sale_repo, test_supplier_repo

Service tests:
  test_stock_service, test_backup_service, test_export_service
  test_audit_service, test_alert_service, test_customer_service
  test_location_service, test_price_list_service
  test_purchase_order_service, test_return_service
  test_sale_service, test_sale_customer, test_supplier_service
  test_undo_service, test_image_service

Core tests:
  test_database, test_migration (V1→V12 chain), test_health
  test_i18n, test_models

════════════════════════════════════════════════════════════════════════════
README — updated to reflect actual v2.3 structure (was describing v2.0)
════════════════════════════════════════════════════════════════════════════

Old README described: 6 services, 6 models, 1 component, 0 pages dir,
                      V5 schema, no controllers, no workers, no test suite

Updated project structure tree covers all new layers:
  controllers/, workers/, pages/, full components/,
  13 models, 13 repos, 21 services, 14 admin panels,
  all 7 new DB tables groups, schema V12

Feature list updated:
  POS / Sales module, Audit / Stocktake module, Price Lists module,
  Purchase Orders module, Returns module, Suppliers module,
  Locations / multi-location stock, Customer CRM, Auto-updater,
  Backup scheduler, Undo transactions, Image attachments,
  Product expiry/warranty tracking, 30+ test modules

Keyboard shortcuts table expanded
Admin panel guide updated to 14 panels
Troubleshooting section updated with logger path + debug mode

════════════════════════════════════════════════════════════════════════════
BUILD & PROJECT
════════════════════════════════════════════════════════════════════════════

requirements.txt    Updated: added fpdf2, PyMuPDF, Pillow, python-barcode,
                    defusedxml; pinned PyQt6==6.10.2, PyInstaller==6.19.0
StockManagerPro.spec Updated hidden imports for all new modules + workers
main.py             Deferred imports for frozen-app compatibility;
                    updated startup sequence; splash + async init
.gitignore          Added logs/, *.db, __pycache__/, dist/, build/
plan.md (566 lines) Full development roadmap with phase tracking
LICENSE             MIT — updated year

Screenshots added (img/):
  scr-inventory, scr-displays, scr-displays2, scr-barcode,
  scr-quickscan, scr-stockops, scr-transaction + 2 UI previews
Icons added (img/icons/): power_icon.svg, receipt_icon.svg, toggle_icon.svg
…ntroller architecture

72 files changed, 32 155 insertions, 10 760 deletions
Previous release: v2.2 (schema V5, products/stock_entries legacy, monolithic main_window)

════════════════════════════════════════════════════════════════════════════
DATABASE — Schema evolved from V5 → V12 (7 new migration paths)
════════════════════════════════════════════════════════════════════════════

V6  part_type_colors table; rebuild inventory_items with UNIQUE(model_id,
    part_type_id, color) constraint; drop legacy products, stock_entries,
    product_transactions, stock_transactions tables (Phase C complete)

V7  image_path column on inventory_items for product photo storage

V8  expiry_date + warranty_date columns on inventory_items; create
    locations table; location_stock join table; stock_transfers table;
    seed default "Main" location; backfill location_stock from item.stock

V9  sales + sale_items tables (POS foundation)

V10 customers table (name, phone, email, address); customer_id FK on sales;
    idx_customers_name + idx_customers_phone

V11 purchase_orders (PO lifecycle DRAFT→SENT→PARTIAL→RECEIVED);
    purchase_order_lines (line items with received_qty tracking);
    returns table (action: RESTOCK/WRITE_OFF, refund_amount)

V12 suppliers enhanced (contact_name, phone, email, rating, updated_at);
    supplier_items (cost_price, lead_days, supplier sku, UNIQUE);
    inventory_audits (stocktake cycles, status, started/completed_at);
    audit_lines (system_qty, counted_qty, difference per item per audit);
    price_lists (active/draft/archived status);
    price_list_items (per-item override prices, UNIQUE constraint)

Current _SCHEMA_VERSION = "12"

Full table inventory (22 tables):
  app_config, categories, part_types, phone_models, part_type_colors,
  inventory_items, inventory_transactions,
  suppliers, supplier_items,
  locations, location_stock, stock_transfers,
  customers,
  sales, sale_items,
  purchase_orders, purchase_order_lines,
  returns,
  inventory_audits, audit_lines,
  price_lists, price_list_items

════════════════════════════════════════════════════════════════════════════
ARCHITECTURE — Zero-freeze async engine + controller decomposition
════════════════════════════════════════════════════════════════════════════

app/ui/workers/worker_pool.py (NEW)
  • QThreadPool-backed POOL singleton with keyed task cancellation
  • _Signals(QObject) carrier — QRunnable cannot host Qt signals directly
  • threading.Event cancellation flags discard stale in-flight results
  • submit(key, fn, on_result, on_error) — named task slots, cancel on resubmit
  • submit_debounced(key, fn, on_result, delay_ms) — QTimer-gated debounce
  • cancel(key) / _invalidate(key) / _cleanup(key, ev) full lifecycle

app/ui/workers/data_worker.py (NEW)
  • DataWorker(QRunnable) generic background fetch with _Signals carrier
  • Used by StartupController for two-phase async startup

app/ui/workers/update_worker.py (NEW)
  • UpdateWorker(QRunnable) for manifest-based version checks off main thread

app/ui/controllers/ (NEW directory — 7 controllers)

  nav_controller.py (137 lines)
    Registry-based navigation: register(key, page_index, on_activate)
    go(key) switches QStackedWidget + fires on_activate callback
    toggle_sidebar() owns visibility + ☰/× glyph
    rebuild_matrix_tabs() / retranslate_matrix_tabs()
    navigated = pyqtSignal(str); current: str property

  startup_controller.py (129 lines)
    Two-phase async startup: begin() → singleShot(0, _load_summary)
    Phase 1 DataWorker: summary KPIs → dashboard + analytics + alerts paint
    Phase 2 singleShot(0, _load_inventory) after analytics paints
    ready = pyqtSignal(); _drop(worker) safe keep-alive removal

  update_controller.py
    Manifest-based update check via UpdateWorker; badge wiring via signal

  alert_controller.py
    StockAlertCounts refresh via POOL; badge + panel wiring

  stock_ops.py
    Fixed _check_alerts → _alert_ctrl.refresh() (×2)
    Fixed _nav_to("nav_transactions") → _nav_ctrl.go("nav_transactions")

  bulk_ops.py
    Removed 2 redundant _check_alerts() calls (_refresh_all covers alerts)

  inventory_ops.py
    Inventory-specific operations extracted from main_window

app/ui/main_window.py (2 263 → 572 lines)
  All DB calls removed from main thread; every fetch via POOL
  _refresh_all() fires 6 parallel POOL jobs simultaneously
  _deferred_retranslate_refresh() routes analytics + transactions via POOL
  _on_summary_ready() syncs _inv_page._cached_count
  NavController instantiated in _build_ui() after stack is complete
  Module-level singletons: _cat_repo, _txn_repo, _item_repo, _stock_svc, _alert_svc

════════════════════════════════════════════════════════════════════════════
CORE MODULES
════════════════════════════════════════════════════════════════════════════

theme.py
  set_theme() defers setStyleSheet via QTimer.singleShot(0) — kills freeze
  Internal state (tokens, is_dark) updates immediately for correct animation
  _apply_ss(root, ss) with RuntimeError guard for destroyed widgets
  Pro Dark glass search bar: transparent bg, rgba(255,255,255,45) rim
  Pro border radii: br_card/btn/input/table/tab = 6/4/4/6/6px

i18n.py
  Merge conflict resolved; translations expanded to cover all new pages,
  dialogs, admin panels, controllers, and components (EN/DE/AR)

colors.py (NEW)
  PALETTE: 24-color OrderedDict Red → White
  hex_for(name), is_light(hex_str), all_names()
  Used by ColorPickerDialog, ProductDetailBar, color swatch rendering

version.py (NEW)
  APP_VERSION = "2.0.0" — single source of truth
  UPDATE_MANIFEST_URL for remote manifest check

health.py (NEW)
  DB health queries for AboutPanel system info display

logger.py (NEW)
  Structured logging with rotation; level from app_config
  get_logger(__name__) used across all modules

database.py (1 112 lines)
  Full V1→V12 migration chain as documented above
  _DDL defines all 22 tables with indexes in one idempotent script
  _ensure_all_entries() populates matrix (model × part_type) gaps
  load_demo_data() seeds Galaxy@Phone demo with all 3 languages

════════════════════════════════════════════════════════════════════════════
MODELS — 9 new domain models (13 total, product.py deleted)
════════════════════════════════════════════════════════════════════════════

audit.py          AuditCycle(id, name, status, notes, started_at, completed_at)
                  AuditLine(item_id, system_qty, counted_qty, difference)
                  AuditStatus enum: IN_PROGRESS / COMPLETED / CANCELLED

customer.py       Customer(name, phone, email, address, notes, is_active)

location.py       Location(name, description, is_default, is_active)
                  LocationStock(item_id, location_id, quantity)

price_list.py     PriceList(name, description, is_active, created_at)
                  PriceListItem(price_list_id, item_id, price)
                  PriceListStatus enum

purchase_order.py PurchaseOrder(po_number, supplier_id, status, notes)
                  POLineItem(po_id, item_id, quantity, cost_price, received_qty)
                  POStatus enum: DRAFT/SENT/PARTIAL/RECEIVED

return_item.py    Return(sale_id, item_id, quantity, reason, action, refund_amount)
                  ReturnAction enum: RESTOCK / WRITE_OFF

sale.py           Sale(customer_name, customer_id, total_amount, discount, note)
                  SaleItem(sale_id, item_id, quantity, unit_price, cost_price)

supplier.py       Supplier(name, contact_name, phone, email, address, rating)

item.py           Added: image_path, expiry_date, warranty_date fields

product.py        DELETED — Phase C migration complete

════════════════════════════════════════════════════════════════════════════
REPOSITORIES — 9 new, 4 updated, 1 deleted (13 total)
════════════════════════════════════════════════════════════════════════════

NEW:
  audit_repo.py          CRUD for inventory_audits + audit_lines
                         get_active(), get_completed(), get_lines(audit_id)
  customer_repo.py       Customer CRUD; get_by_phone(); purchase_history(id)
  location_repo.py       Location CRUD; get_stock(item_id); occupancy query
  price_list_repo.py     PriceList CRUD; get_active(); get_items(list_id)
  purchase_order_repo.py PO CRUD + lines; get_by_status(); receive_line()
  return_repo.py         Return CRUD; get_by_sale(); get_by_item()
  sale_repo.py           Sale CRUD; get_items(sale_id); revenue_by_date()
                         daily_summary(); top_selling_items()
  supplier_repo.py       Supplier CRUD; get_items(supplier_id); link/unlink item
  customer_repo.py       Customer CRUD with phone index search

UPDATED:
  item_repo.py       Added supplier_id, location_id, image_path, expiry/warranty
                     filters; get_low_stock(); get_expiring(days)
  transaction_repo.py  get_filtered(filters) → paginated; count_filtered();
                       get_summary_stats() for transaction strip
  category_repo.py   Updated for V6+ schema
  model_repo.py      Updated for V6+ schema

DELETED:
  product_repo.py    Removed — legacy products table dropped in V6

════════════════════════════════════════════════════════════════════════════
SERVICES — 14 new, 5 updated (21 total)
════════════════════════════════════════════════════════════════════════════

NEW:
  audit_service.py         create_cycle(); record_count(audit_id, item_id, qty)
                           finalize(audit_id) — calculates variances, commits
  backup_scheduler.py      BackupScheduler(QObject) 5-min QTimer; off-thread
  customer_service.py      Customer CRUD + purchase_history aggregation
  image_service.py         Product image import; resize via Pillow; path cache
  location_service.py      Location CRUD; transfer_stock(item, from, to, qty)
  price_list_service.py    apply_price_list(list_id) bulk-updates inventory
                           sell_price from price_list_items
  purchase_order_service.py create_po(); send(); receive_line(line_id, qty)
                            → auto stock-in via StockService
  receipt_service.py       PDF receipt generation via fpdf2; logo embed
  return_service.py        process_return() → reverse transaction +
                           refund record; RESTOCK vs WRITE_OFF dispatch
  sale_service.py          checkout(cart) → deduct stock per item →
                           create Sale + SaleItems; discount handling
  supplier_service.py      Supplier CRUD; link_item / unlink_item
  undo_service.py          undo_transaction(txn_id) — reverses last
                           IN/OUT/ADJUST per item with inverse operation
  update_service.py        Manifest-based version check; UpdateManifest
                           dataclass; download(url, progress_cb)
  export_service.py        CSV/Excel/JSON export for all entity types
  import_service.py        CSV import with row-level validation + error report

UPDATED:
  alert_service.py    StockAlertCounts(low, out, total, has_out) dataclass
  backup_service.py   auto_backup(retain, backup_dir) with retention purge
  stock_service.py    undo support; supplier_id/location_id propagation
  report_service.py   PDF reports via fpdf2 (transactions, inventory summary)
  barcode_gen_service.py  Updated for V6+ unified inventory schema

════════════════════════════════════════════════════════════════════════════
UI COMPONENTS — 22 components in app/ui/components/
════════════════════════════════════════════════════════════════════════════

footer_bar.py        FooterBar(QFrame) level-aware status + filter hint
header_bar.py        Glass search bar; left-fixed at x=224 aligning to content
theme_toggle.py      ThemeToggle animated sun/moon; freeze fixed via singleShot
language_switcher.py LanguageSwitcher animated dropdown; _LangRow hover paint;
                     _TriggerButton; live language switch with RTL support
update_banner.py     UpdateBanner slide via QPropertyAnimation on maximumHeight;
                     Install / Skip / Remind Later; QProgressBar for download
notification_panel.py NotificationPanel: alert counts + update badge +
                      low-stock quick list; view_alerts_requested signal
product_detail_bar.py ProductDetailBar: identity, stock, price, min/diff;
                       sparkline trend; quick IN/OUT/ADJUST/Edit/Delete buttons;
                       color swatch via is_light()
product_detail.py    ProductDetail compact side panel
product_table.py     ProductTable(QTableWidget) responsive column hiding
transaction_table.py TransactionTable with copy-row context menu
responsive_table.py  _TableResizer(QObject) + make_table_responsive() utility
toast.py             ToastManager floating non-blocking notifications with stack
loading_overlay.py   LoadingOverlay spinner for async page loads
splash_screen.py     SplashScreen with progress bar and branding
sidebar.py           Sidebar extracted from main_window; nav button registry
charts.py            Reusable chart widgets for analytics / dashboard
collapsible_section.py CollapsibleSection for admin panels
empty_state.py       EmptyState icon + title + subtitle widget
mini_txn_list.py     MiniTxnList compact transaction list for detail panels
barcode_line_edit.py BarcodeLineEdit with scan-signal debounce
filter_bar.py        FilterBar reusable filter strip component
dashboard_widget.py  SummaryCard setUpdatesEnabled batching; child selector QSS
matrix_widget.py     Updated for V6+ color constraint

════════════════════════════════════════════════════════════════════════════
UI PAGES — 11 pages in app/ui/pages/
════════════════════════════════════════════════════════════════════════════

analytics_page.py   _fetch_all_data() single BG job: get_summary() once (was 3×)
                    _apply_all_data() dispatches 8 apply methods on main thread
                    retranslate() no longer triggers 8 sync DB queries

transactions_page.py fetch_filtered() → {rows, total, stats} off main thread
                     load_results(data) main-thread only
                     _apply_filters() → POOL.submit_debounced("txn_filter")
                     Summary strip: total, IN, OUT, net with color labels
                     Load More pagination with offset tracking

inventory_page.py   fetch_filtered(filters) BG-safe; load_items(items) main-thread
                    _cached_count for dashboard KPI sync

audit_page.py (677 lines)
                    AuditListView: cycle list, status filters All/Active/Completed
                    AuditDetailView: item-by-item count entry, variance column
                    NewAuditDialog: name + description
                    KPI cards: total, active, completed, avg variance %

price_lists_page.py (673 lines)
                    _PriceListsOverviewView: KPI cards, All/Active/Draft/Archived tabs
                    _PriceListDetailView: items table, bulk % markup, apply to inventory
                    PriceListService.apply_price_list(list_id) integration

sales_page.py (600 lines)
                    SalesPage: KPI cards + sales history with date filter
                    POSDialog: product picker, cart, qty adjust, running total, checkout
                    ReceiptService → PDF receipt on checkout

purchase_orders_page.py (563 lines)
                    Full PO lifecycle: DRAFT → SENT → PARTIAL → RECEIVED
                    _PODialog: supplier select + line items
                    _AddItemDialog: add inventory items to PO lines
                    Receive PO → auto stock-in via StockService
                    KPI cards: total, draft, sent, received

returns_page.py (339 lines)
                    _ReturnDialog: item select, reason, refund amount
                    ReturnService: reverse txn + refund record
                    KPI cards: total, approved, written-off, refunded

suppliers_page.py (536 lines)
                    _SupplierDialog: supplier CRUD with contact + rating
                    _SupplierItemsDialog: link/unlink inventory items
                    KPI cards + search

reports_page.py     ReportsPage: type picker → PDF via ReportService
barcode_gen_page.py Updated for V6+ unified schema

════════════════════════════════════════════════════════════════════════════
UI DIALOGS
════════════════════════════════════════════════════════════════════════════

product_dialogs.py (expanded)
  ModernDialog(QDialog) — themed header + action footer
  FormField(QWidget) — label + input with validation state
  QuantitySpin(QWidget) — +/- spin with keyboard input
  ColorPickerDialog — 24-color grid from colors.PALETTE
  ColorButton(QPushButton) — live color swatch icon
  ProductDialog — full add/edit: barcode, color, min stock, pricing, image
  StockOpDialog — IN/OUT/ADJUST with QuantitySpin + reason
  LowStockDialog — alert list with product_selected pyqtSignal

dialog_base.py (NEW)    DialogBase(QDialog) shared modal foundation
bulk_price_dialog.py (NEW) Bulk % price change across selection
price_list_dialogs.py (NEW) PriceListDialog + PriceListItemDialog
help_dialog.py (NEW)    Tabbed documentation dialog

Admin dialog expanded to 14 panels:
  admin_dialog.py (274 lines) + preview_banner_requested pyqtSignal

  Existing panels updated:
    shop_settings_panel, categories_panel, part_types_panel,
    models_panel, scan_settings_panel, color_picker_widget

  New panels:
    about_panel.py (547 lines)
      System info: schema V12, DB size, OS, Python, PyQt6 build
      UpdateBanner live-preview trigger for testing
    backup_panel.py
      Manual backup trigger + retention-managed backup list
    db_tools_panel.py
      VACUUM, integrity check, schema version display
    import_export_panel.py
      CSV/JSON import + export per entity type
    locations_panel.py (362 lines)
      _LocationDialog CRUD (name, description, default flag)
      KPI card, searchable table
    customers_panel.py
      Customer CRUD with purchase history
    suppliers_panel.py
      Supplier CRUD in admin context

════════════════════════════════════════════════════════════════════════════
UI TABS
════════════════════════════════════════════════════════════════════════════

quick_scan_tab.py (NEW) QuickScanTab: barcode scan → stock-op dispatch
stock_ops_tab.py (NEW)  StockOpsTab extracted from main_window
base_tab.py             Updated for V6+ schema + new component structure
matrix_tab.py           Updated for color constraint + part_type_colors

════════════════════════════════════════════════════════════════════════════
TESTS — full pytest suite (app/tests/, 30+ test modules)
════════════════════════════════════════════════════════════════════════════

Infrastructure:
  conftest.py        in-memory SQLite fixtures, full schema, seeded data
  __init__.py        test package helpers
  run_tests.py       CLI runner with coverage report

Repository tests:
  test_item_repo, test_transaction_repo, test_category_repo, test_model_repo
  test_audit_repo, test_customer_repo, test_location_repo
  test_price_list_repo, test_purchase_order, test_sale_repo, test_supplier_repo

Service tests:
  test_stock_service, test_backup_service, test_export_service
  test_audit_service, test_alert_service, test_customer_service
  test_location_service, test_price_list_service
  test_purchase_order_service, test_return_service
  test_sale_service, test_sale_customer, test_supplier_service
  test_undo_service, test_image_service

Core tests:
  test_database, test_migration (V1→V12 chain), test_health
  test_i18n, test_models

════════════════════════════════════════════════════════════════════════════
README — updated to reflect actual v2.3 structure (was describing v2.0)
════════════════════════════════════════════════════════════════════════════

Old README described: 6 services, 6 models, 1 component, 0 pages dir,
                      V5 schema, no controllers, no workers, no test suite

Updated project structure tree covers all new layers:
  controllers/, workers/, pages/, full components/,
  13 models, 13 repos, 21 services, 14 admin panels,
  all 7 new DB tables groups, schema V12

Feature list updated:
  POS / Sales module, Audit / Stocktake module, Price Lists module,
  Purchase Orders module, Returns module, Suppliers module,
  Locations / multi-location stock, Customer CRM, Auto-updater,
  Backup scheduler, Undo transactions, Image attachments,
  Product expiry/warranty tracking, 30+ test modules

Keyboard shortcuts table expanded
Admin panel guide updated to 14 panels
Troubleshooting section updated with logger path + debug mode

════════════════════════════════════════════════════════════════════════════
BUILD & PROJECT
════════════════════════════════════════════════════════════════════════════

requirements.txt    Updated: added fpdf2, PyMuPDF, Pillow, python-barcode,
                    defusedxml; pinned PyQt6==6.10.2, PyInstaller==6.19.0
StockManagerPro.spec Updated hidden imports for all new modules + workers
main.py             Deferred imports for frozen-app compatibility;
                    updated startup sequence; splash + async init
.gitignore          Added logs/, *.db, __pycache__/, dist/, build/
plan.md (566 lines) Full development roadmap with phase tracking
LICENSE             MIT — updated year

Screenshots added (img/):
  scr-inventory, scr-displays, scr-displays2, scr-barcode,
  scr-quickscan, scr-stockops, scr-transaction + 2 UI previews
Icons added (img/icons/): power_icon.svg, receipt_icon.svg, toggle_icon.svg
github-actions Bot and others added 15 commits April 21, 2026 09:45
Added
- Admin → Shop Settings → "Show sell totals in matrix" checkbox.
  When off: TOTAL column in the matrix table + value on per-part-type
  cards + grand-total card are all hidden, so shop assistants see stock
  without seeing valuation. Units/stock counts stay visible. Cost mode
  (PIN-gated 👁) overrides — when cost is unlocked, totals stay visible
  so the comparison still makes sense.
- ShopConfig.show_sell_totals persisted via existing app_config key
  mechanism (no DB migration). Typed accessor: is_show_sell_totals.
- Live-update on save: ShopConfig.invalidate() + rebuild_matrix_tabs
  fast path propagate the new state to every realised matrix tab
  instantly. Default ON — existing users see no change until they
  toggle it off.

Fixed
- Float-precision bug: 7 × 22.99 was rendering as €160.9299999999998.
  ShopConfig.format_currency used str(amount) which leaked full float
  representation. Now uses f"{float(amount):,.2f}" — exactly 2 decimals
  plus thousands separator. Single-line fix at source; every money
  display across the app (matrix cells, cards, Quick Scan, Sales, POS,
  Analytics, Reports, Purchase Orders) benefits automatically.
Added
- Admin → Shop Settings → "Show sell totals in matrix" checkbox.
  When off, the matrix TOTAL column + per-part-type card values +
  grand-total card are hidden so shop assistants see stock without
  seeing valuation. Units stay visible either way. Cost mode (PIN-gated
  👁) overrides — with cost unlocked, totals stay visible. Persisted via
  ShopConfig.show_sell_totals (existing app_config key mechanism — no
  migration). Default ON. Live-update on Save via the existing
  settings-close rebuild chain.
- Excel-style fill-down in matrix tabs. Select a source cell (MIN-STOCK,
  SELL, or COST), extend the selection downward via click-drag /
  Shift-click / Ctrl-click, then press Ctrl+D or right-click → "Fill
  Down". The top-left cell's value fills every other selected cell of
  the same field type. Cells of other fields (STOCK, ORDER, TOTAL) are
  skipped silently. Cost fills respect COST_VIS.visible. The whole fill
  is one Undo Command so Ctrl+Z reverts all cells at once. Selection
  mode switched from SingleSelection+SelectRows to
  ExtendedSelection+SelectItems for native drag-select.
- ItemRepository.update_cost_price helper (referenced by fill-down path).

Changed
- All matrix edit dialogs now open with their number field at 0 and
  auto-focus + select-all the input, so the user can start typing
  immediately. Previous value is shown in the dialog body / context
  line rather than pre-filled in the spinbox (would need an extra
  click to clear). Applies to StockOpDialog, ThresholdDialog,
  InventurDialog, and the Sell/Cost QInputDialog.getDouble prompts.
  QuantitySpin gained a showEvent override that focuses + selects its
  inner QLineEdit via QTimer.singleShot(0, …).

Fixed
- Float-precision display bug: 7 × 22.99 was rendering as
  €160.9299999999998 in matrix TOTAL cells and cards. ShopConfig.
  format_currency used str(amount) which leaked full float
  representation. Now uses f"{float(amount):,.2f}" — exactly 2
  decimals plus thousands separator. Single-line fix at source;
  every money display across the app (matrix cells, cards, Quick
  Scan, Sales, POS, Analytics, Reports, Purchase Orders) benefits.
README Schema badge was stuck at V14 because the CI workflow only
stamped the Version badge. Added a second re.sub block that reads
_SCHEMA_VERSION from app/core/database.py and updates the
Schema_V{N}-003B57 badge. Will keep the badge in sync on every
future release.

Manually bumped the current badge to V16 so main reflects the
current schema immediately (v2.4.2 already shipped with the old
badge and the workflow can only rewrite README on a new tag).
Added a new checkbox for showing sell totals in the shop settings, allowing stock counts to be visible without sell valuation.
@ai-quality-gate

Copy link
Copy Markdown

AI Quality Gate — Low Quality

Metric Value
Quality Score 26% (F)

Improvements Needed

  • body-present: No description provided (0/15)
  • pr-description: No PR description provided (0/10)
  • linked-issue: No linked issue (0/10)
  • test-mention: No test mention (0/5)
  • tests-included: No diff provided (0/10)
  • title-length: Title too short (3/10)
  • title-specificity: Title could be more specific (6/10)
  • title-convention: Title does not follow conventional commit format (3/10)
  • diff-size: No diff provided (5/10)
  • single-purpose: No diff provided (5/10)

Please review the suggestions above to improve your contribution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant