Hệ thống Tồn kho Khả dụng & Hóa đơn GTGT

Toàn bộ tài liệu dự án trong một trang: tổng quan nghiệp vụ, kiến trúc kỹ thuật, và sơ đồ hệ thống.

Cập nhật 14/08/2026 · Bản nội bộ — có phần chia việc & báo giá

Dành cho Ban lãnh đạo

Kazuki Ebike · Phòng Kinh doanh

Tồn kho khả dụng theo thời gian thực

Trợ lý trên Telegram trả lời chính xác câu hỏi “còn bao nhiêu xe thật sự bán được” — ngay tại thời điểm hỏi, không phải số của sáng nay.

Vấn đề nằm ở khoảng lệch giữa hai con số

50 AMIS đang hiển thị
nhưng
15 thật sự còn bán được

Trong ngày đã có các lệnh xuất giữ mất 35 xe, nhưng chứng từ chưa kịp lên AMIS. Phòng Kinh doanh nhìn số 50 để chốt đơn, tới lúc xuất kho mới phát hiện không đủ xe — phải đổi cơ cấu đơn, kho và kinh doanh cùng mất thời gian.

Độ trễ này khoảng 3 đến 4 tiếng: dắt xe từ nhiều kho mất 2–3 tiếng, kế toán ghi phiếu lên AMIS thêm 30 phút đến 1 tiếng.

Hệ thống làm gì trong một ngày

  1. Sáng — nhận báo cáo tồn AMIS

    Kế toán gửi file như hiện nay, không đổi thói quen. Hệ thống đọc bảng bằng chương trình chứ không đọc bằng mắt AI, nên không có chuyện nhìn nhầm số.

  2. Lấy đó làm mốc, cộng thêm các lệnh chưa xuất hôm trước

    Những lệnh đã nhắn nhưng chưa làm phiếu xuất vẫn được giữ chỗ tiếp — không bị xóa mất như khi ghi tay.

  3. Mỗi lệnh xuất nhập lên nhóm, trừ tồn ngay lập tức

    Trừ kho thành phẩm trước; thiếu thì mới giữ chỗ ở kho sản xuất và báo rõ là xe chưa xuất được ngay.

  4. Hỏi lúc nào cũng ra số đúng lúc đó

    Kèm luôn nguyên nhân nếu thiếu: đơn nào đã lấy mất màu đó, mỗi đơn bao nhiêu xe.

Bot trả lời như thế nào

ModelMàuSLKho TPKho SXKết quảGhi chú
Libra NewXám Ánh Hồng2 12→100 Đủ
Libra NewGhi Sần4 019→15 Kẹt Kho SXChờ nhập về TP
Slay NewGhi Sần10 03 Thiếu 7 xe Minh Vương lấy 2, Dương Huệ lấy 2
Slay NewXám Ánh Hồng2 00 Hiện tồn 0 Baseline sáng không có màu này

Kho thành phẩm và kho sản xuất luôn tách riêng hai số, kể cả khi một bên bằng 0 — vì hai tình huống này xử lý khác nhau.

Ba nguyên tắc an toàn

  • Không bao giờ tự đoán tên xe hay màu

    Gõ sai hoặc viết tắt lạ thì hệ thống hỏi lại, không tự suy ra. Thà hỏi thừa còn hơn trừ nhầm kho.

  • Mọi con số do máy tính, không do AI nhẩm

    Phần AI chỉ đọc chữ và viết câu trả lời. Toàn bộ phép cộng trừ chạy bằng cơ sở dữ liệu.

  • Hệ thống không tự hủy giữ chỗ của ai

    Ai lên lệnh trước giữ trước. Xe kẹt lâu thì hệ thống nhắc đúng bộ phận liên quan, còn quyết định vẫn là của anh.

Những gì hệ thống không làm ở giai đoạn đầu

  • Không nối trực tiếp vào MISA AMIS — vẫn dùng file kế toán gửi như hiện nay.
  • Không quản lý theo từng số khung ở phần tồn kho — phần đó AMIS đang làm.
  • Không tự tính ngày xe có tem đăng kiểm.
  • Nhân viên kinh doanh chưa hỏi bot trực tiếp — mọi câu hỏi vẫn qua anh và ban lãnh đạo.

Cách chứng minh hệ thống chạy đúng

Chúng tôi không đề nghị anh tin vào lời hứa “chính xác 100%”. Mỗi sáng khi có báo cáo AMIS mới, hệ thống tự so số nó dự đoán cuối ngày hôm trước với số AMIS thật và báo chênh lệch theo từng mã xe.

Chạy song song một đến hai tuần: hệ thống tính và báo, nhưng anh vẫn quyết như hiện nay. Khi chênh lệch bằng 0 liên tục thì mới chuyển sang dùng chính thức.

Bản chạy thử sẽ dùng 17 đơn hàng thật anh đã gửi trong quá trình trao đổi, đối chiếu với chính con số anh đã tính tay — không demo bằng số liệu dựng sẵn.

Lộ trình

Giai đoạn 1 — Tồn kho khả dụng

sẵn sàng làm

Toàn bộ nội dung trong trang này. Nghiệp vụ đã chốt xong với anh, không còn điểm nào phải chờ. Phạm vi rõ nên báo giá cố định được.

Giai đoạn 2 — Tự động xuất hóa đơn GTGT

chờ khảo sát

Đối soát số khung và số máy từ bốn nguồn trước khi xuất hóa đơn, để kế toán không phải gõ tay từng chiếc. Cần buổi làm việc với chị Trang trước khi báo giá — chưa hỏi người phụ trách thì con số đưa ra chỉ là ước đoán.

Tài liệu trao đổi nội bộ · Kazuki Ebike (Công ty Dương Tiến Phát) · Cập nhật 14/08/2026

ARCHITECTURE-SPINE — hợp đồng ràng buộc cho người xây

Architecture Spine — Kazuki AI Assistant

Tài liệu này là nguồn schema và ranh giới duy nhất. Nó thay thế hoàn toàn bản spine 13/08. architecture-consensus.md hạ cấp thành nhật ký quyết định — khi hai bên chỏi nhau, spine thắng.

Design Paradigm

Hexagonal (Ports & Adapters), lõi tất định thuần, AI nằm ngoài lõi.

Ba vòng, phụ thuộc chỉ đi vào trong:

AI LLM là một adapter, không bao giờ là lõi. Nó chỉ làm hai việc: bóc chuỗi thô từ văn bản gõ tay thành JSON, và diễn đạt danh sách mã lỗi đã tính sẵn thành câu tiếng Việt. Mọi con số đi qua lõi tất định.

Ánh xạ paradigm sang namespace CSDL: core_ (dữ liệu dùng chung), inv_ (Module 01), fin_ (Module 02).

Invariants & Rules

Hướng phụ thuộc — mũi tên là "được phép phụ thuộc vào":

CẤM adapters/telegram · sqlite · pdf · excel · llm ports/giao diện trừu tượng core/hàm thuần, tất định app/điều phối use-case
CẤM adapters/telegram · sqlite · pdf · excel · llm ports/giao diện trừu tượng core/hàm thuần, tất định app/điều phối use-case

AD-1 — Python là bên ghi CSDL duy nhất, Node là relay thuần [ADOPTED]

AD-2 — Một file SQLite dùng chung, cấu hình bắt buộc [ADOPTED]

AD-3 — Spine là nguồn schema duy nhất

AD-4 — Định danh xe là CẶP (số khung, số máy)

Căn cứ: dữ liệu đăng kiểm thật cho thấy xe cùng lô mang số khung liền nhau (RNKY6EKUMSDA02621, ...02622), cùng model, cùng màu. Sai một chữ số là rơi trúng xe kế bên và mọi kiểm tra thuộc tính đều lọt. Dãy số máy chạy độc lập với dãy số khung nên cặp này mới là kiểm tra chéo thật. Tem trên xe có sẵn cả hai số.

AD-5 — Một đại lý có nhiều pháp nhân

AD-6 — Tồn khả dụng là derived state, không bao giờ là biến đếm

available_tp = baseline.stock_tp − Σ qty_tp (đơn ACTIVE, còn hiệu lực) available_sx = baseline.stock_sx − Σ qty_sx (đơn ACTIVE, còn hiệu lực)

Không kẹp về 0. Số âm là tín hiệu phải báo, không phải lỗi để giấu.

AD-7 — Carry-forward theo tình trạng xuất phiếu, không theo mốc thời gian

AD-8 — Không có chuyển trạng thái nào theo thời gian

AD-9 — Tách chế độ tin cậy theo module

AD-10 — Chống trùng hóa đơn bằng partial unique index, đảo ngược được

AD-11 — Hóa đơn chứa được dòng không có số khung

AD-12 — LLM không bao giờ sinh định danh hay ô dữ liệu

AD-13 — Khớp SKU: chỉ ba kết cục, không có điểm số

AD-14 — Vắng mặt trong báo cáo AMIS nghĩa là tồn 0, không phải sai tên

AD-15 — Đối soát 4 nguồn dựng tập ứng viên TỪ FILE A

AD-16 — Hợp đồng đọc chatlog

AD-17 — Mọi số liệu báo ra đều kèm nguồn gốc

AD-18 — Đối soát Delta mỗi sáng là điều kiện phát hành

AD-19 — Luôn có đường can thiệp thủ công

AD-20 — Bộ Golden-Master dựng từ dữ liệu thật

AD-21 — Chỉ một thành phần được tạo bản ghi core_skus

Vì sao cần: nếu không có luật này, hai thành phần đều tuân thủ đủ mọi AD khác vẫn có thể tạo ra hai sku_id cho cùng một chiếc xe — một từ chữ trong file AMIS, một từ chữ trong file đăng kiểm — và tồn kho bị chia đôi mà không ai thấy.

AD-22 — core_vehicles trỏ về core_skus, không mang tên model/màu tự do

Vì sao cần: file đăng kiểm ghi màu thương mại ở cột "Màu xe (Thực tế)" còn báo cáo AMIS dùng cách viết riêng. Nếu mỗi bảng giữ một chuỗi tự do thì phép đối chiếu model/màu giữa core_vehicles và dòng LXN hoặc luôn trượt, hoặc trùng nhầm — cả hai đều hỏng mà vẫn "đúng luật".

AD-23 — Số máy do Thủ kho báo là dữ liệu khai báo, core_vehicles là thẩm quyền

Consistency Conventions

ConcernConvention
Đặt tên bảngTiền tố namespace bắt buộc: core_ dùng chung, inv_ Module 01, fin_ Module 02. Số nhiều, snake_case, tiếng Anh.
Đặt tên cộtsnake_case tiếng Anh. Khóa ngoại là <bảng_số_ít>_id. Cột thời gian kết thúc bằng _at, cột ngày kết thúc bằng _date.
Thời gianISO8601 UTC lưu dạng TEXT. Không dùng CURRENT_TIMESTAMP của SQLite (không có T, không có Z). Sinh ở tầng ứng dụng.
TiềnINTEGER, đơn vị đồng. Cấm REAL.
Số lượngINTEGER. Cấm số thực trong mọi phép tính tồn kho.
Số khung / số máy / mã hàngÉp kiểu chuỗi ngay khi đọc Excel. pandas và openpyxl tự suy kiểu, một số khung toàn chữ số sẽ thành 1.23e+16 và mất dữ liệu trước khi bất kỳ kiểm tra nào chạy.
Nạp dữ liệuAppend-only kèm ingest_id. Không sửa đè bản ghi cũ. Nạp lại cùng một file phải idempotent (khóa tự nhiên từ chính file).
TransactionBEGIN IMMEDIATE cho mọi đường ghi. Parse, gọi LLM, sinh file đều nằm ngoài transaction.
Mã lỗiSCREAMING_SNAKE_CASE, một mã cho một nguyên nhân. Tầng lõi trả mã; tầng LLM diễn đạt thành câu.
Ngôn ngữĐịnh danh trong code 100% tiếng Anh. Văn bản gửi người dùng 100% tiếng Việt.
Mũi tên trong tài liệuKý tự Unicode . Cấm LaTeX.

Stack

Đã kiểm chứng trên web ngày 14/08/2026. Đây là seed — khi có code thì code làm chủ.

NameVersion
Python3.12.3 (bản hệ thống sẵn có)
SQLite3.45.1 (đi kèm Python 3.12.3) — đã chạy thử STRICT trên máy, đạt; STRICT cần ≥ 3.37
pdfplumber0.11.10
openpyxl3.1.5
rapidfuzz3.14.5 — chỉ dùng sinh gợi ý cho người, xem AD-13
uv0.12.3 (quản lý môi trường & phụ thuộc)
Node.js + AI Gatewayhiện trạng đã có, relay thuần — không đổi [ADOPTED]

Structural Seed

Sơ đồ khối hệ thống

Telegram Python Engine — tiến trình ghi duy nhất socket cục bộ AI Gateway (Node.js)relay thuần — KHÔNG chạm CSDL kazuki.dbSQLite WAL Báo cáo Telegram · File upload MISA · Cảnh báo Nhóm LXN-4936275198 Nhóm Tồn TP-5335588143 Nhóm Đăng kiểm app/ điều phối use-case core/ lõi tất định thuần adapters/ sqlite · pdf · excel · llm
Telegram Python Engine — tiến trình ghi duy nhất socket cục bộ AI Gateway (Node.js)relay thuần — KHÔNG chạm CSDL kazuki.dbSQLite WAL Báo cáo Telegram · File upload MISA · Cảnh báo Nhóm LXN-4936275198 Nhóm Tồn TP-5335588143 Nhóm Đăng kiểm app/ điều phối use-case core/ lõi tất định thuần adapters/ sqlite · pdf · excel · llm

Thực thể lõi

core_skus (no attributes) core_sku_aliases (no attributes) inv_baseline_stocks (no attributes) inv_order_items (no attributes) core_vehicles (no attributes) core_customers (no attributes) core_customer_entities (no attributes) inv_orders (no attributes) inv_order_vehicles (no attributes) fin_batch_items (no attributes) inv_baselines (no attributes) fin_upload_batches (no attributes) được đo tồn được đặt phân loại có N pháp nhân đặt hàng được gán lên hóa đơn gồm gồm khớp xe thật gồm
core_skus (no attributes) core_sku_aliases (no attributes) inv_baseline_stocks (no attributes) inv_order_items (no attributes) core_vehicles (no attributes) core_customers (no attributes) core_customer_entities (no attributes) inv_orders (no attributes) inv_order_vehicles (no attributes) fin_batch_items (no attributes) inv_baselines (no attributes) fin_upload_batches (no attributes) được đo tồn được đặt phân loại có N pháp nhân đặt hàng được gán lên hóa đơn gồm gồm khớp xe thật gồm

Schema chuẩn

Namespace core_inv_ khai báo đầy đủ vì Module 01 phải code được ngay. Namespace fin_ khai báo tới mức invariant — chi tiết cột chờ file mẫu MISA.

-- ══ CORE: dùng chung 2 module ══
CREATE TABLE core_skus (
  sku_id          INTEGER PRIMARY KEY,
  model_name      TEXT NOT NULL,
  color_name      TEXT NOT NULL,
  merged_group_id TEXT NOT NULL,   -- VC GT + VC GT 1 dùng chung 1 group
  created_at      TEXT NOT NULL,
  UNIQUE(model_name, color_name)
) STRICT;

CREATE TABLE core_sku_aliases (
  alias_id         INTEGER PRIMARY KEY,
  sku_id           INTEGER NOT NULL REFERENCES core_skus(sku_id),
  normalized_alias TEXT NOT NULL UNIQUE,  -- đã hạ chữ thường, gom khoảng trắng
  raw_alias        TEXT NOT NULL,
  confirmed_by     TEXT,                  -- alias mới chỉ thêm khi người xác nhận
  confirmed_at     TEXT
) STRICT;

CREATE TABLE core_customers (
  customer_id            INTEGER PRIMARY KEY,
  customer_code          TEXT NOT NULL UNIQUE,
  shipping_customer_name TEXT NOT NULL     -- đại lý NHẬN xe
) STRICT;

CREATE TABLE core_customer_entities (   -- AD-5: 1 đại lý N pháp nhân
  entity_id       INTEGER PRIMARY KEY,
  customer_id     INTEGER NOT NULL REFERENCES core_customers(customer_id),
  legal_name      TEXT NOT NULL,
  tax_code        TEXT NOT NULL,
  invoice_address TEXT,
  is_active       INTEGER NOT NULL DEFAULT 1,
  UNIQUE(customer_id, tax_code)
) STRICT;

CREATE TABLE core_vehicles (            -- nguồn Đăng kiểm
  vin            TEXT PRIMARY KEY,      -- 17 ký tự, 9 đầu là mã kiểu loại theo model
  engine_no      TEXT NOT NULL,         -- AD-4: bắt buộc khớp cùng vin; đây là THẨM QUYỀN (AD-23)
  sku_id         INTEGER NOT NULL REFERENCES core_skus(sku_id),  -- AD-22
  raw_model_text TEXT NOT NULL,         -- lấy từ HEADER file, không phải cột; chỉ truy vết
  raw_color_text TEXT NOT NULL,         -- lấy cột "Màu xe (Thực tế)", KHÔNG lấy "Màu sơn"
  cert_no        TEXT,
  issued_date    TEXT,
  ingest_id      INTEGER NOT NULL
) STRICT;

-- ══ INV: Module 01 ══
CREATE TABLE inv_baselines (
  baseline_id      INTEGER PRIMARY KEY,
  business_date    TEXT NOT NULL,
  received_at      TEXT NOT NULL,
  effective_at     TEXT NOT NULL,   -- AD-7: file sửa trong ngày KẾ THỪA, không nhảy
  is_day_open      INTEGER NOT NULL,
  supersedes_id    INTEGER REFERENCES inv_baselines(baseline_id),
  source_file_path TEXT NOT NULL
) STRICT;

CREATE TABLE inv_baseline_stocks (
  stock_id    INTEGER PRIMARY KEY,
  baseline_id INTEGER NOT NULL REFERENCES inv_baselines(baseline_id),
  sku_id      INTEGER NOT NULL REFERENCES core_skus(sku_id),
  stock_tp    INTEGER NOT NULL DEFAULT 0,
  stock_sx    INTEGER NOT NULL DEFAULT 0,
  UNIQUE(baseline_id, sku_id)
) STRICT;

CREATE TABLE inv_orders (
  order_id               INTEGER PRIMARY KEY,
  tg_chat_id             TEXT NOT NULL,
  tg_msg_id              TEXT NOT NULL,
  issuer_id              TEXT NOT NULL,
  customer_id            INTEGER REFERENCES core_customers(customer_id),
  tax_entity_id          INTEGER REFERENCES core_customer_entities(entity_id),
  scheduled_invoice_date TEXT,
  order_type             TEXT NOT NULL DEFAULT 'STANDARD'
                         CHECK(order_type IN ('STANDARD','DIRECT_TRANSFER')),
  status                 TEXT NOT NULL DEFAULT 'ACTIVE'
                         CHECK(status IN ('ACTIVE','CANCELLED')),
  created_at             TEXT NOT NULL,
  UNIQUE(tg_chat_id, tg_msg_id)       -- AD-16: khóa idempotency
) STRICT;

CREATE TABLE inv_order_items (
  item_id        INTEGER PRIMARY KEY,
  order_id       INTEGER NOT NULL REFERENCES inv_orders(order_id),
  sku_id         INTEGER NOT NULL REFERENCES core_skus(sku_id),
  qty_tp         INTEGER NOT NULL DEFAULT 0,
  qty_sx         INTEGER NOT NULL DEFAULT 0,
  fulfil_status  TEXT NOT NULL DEFAULT 'RESERVED'
                 CHECK(fulfil_status IN ('RESERVED','HOLD_SX_PENDING','SHIPPED','SETTLED','RELEASED'))
) STRICT;

CREATE TABLE inv_order_vehicles (       -- Nguồn 3: Thủ kho xác nhận, dùng chung 2 module
  item_id      INTEGER NOT NULL REFERENCES inv_order_items(item_id),
  vin          TEXT NOT NULL REFERENCES core_vehicles(vin),
  engine_no    TEXT NOT NULL,           -- AD-4: lưu để đối chiếu cặp
  confirmed_by TEXT NOT NULL,
  confirmed_at TEXT NOT NULL,
  input_kind   TEXT NOT NULL CHECK(input_kind IN ('TEXT','OCR','SELECT')),
  PRIMARY KEY (item_id, vin)
) STRICT;

CREATE TABLE inv_bot_messages (
  log_id              INTEGER PRIMARY KEY,
  correlation_id      TEXT NOT NULL UNIQUE,  -- sinh ở client, chống ghi trùng khi retry
  telegram_message_id TEXT,
  chat_id             TEXT NOT NULL,
  message_type        TEXT NOT NULL,
  payload_json        TEXT NOT NULL,
  created_at          TEXT NOT NULL
) STRICT;

-- ══ FIN: Module 02 — mức invariant ══
-- fin_misa_stockout   : FILE A đã parse (AD-15), append-only kèm ingest_id
-- fin_misa_invoiced   : Báo cáo 4a đã parse, dùng xác nhận hóa đơn đã phát hành
-- fin_upload_batches  : mỗi lần sinh file = 1 batch, có file_sha256 và người tạo
CREATE TABLE fin_batch_items (
  batch_item_id         INTEGER PRIMARY KEY,          -- AD-11: khóa thay thế
  batch_id              INTEGER NOT NULL REFERENCES fin_upload_batches(batch_id),
  line_type             TEXT NOT NULL CHECK(line_type IN ('VEHICLE','ACCESSORY')),
  vin                   TEXT REFERENCES core_vehicles(vin),   -- NULL với phụ kiện
  engine_no             TEXT,                          -- AD-4
  item_id               INTEGER REFERENCES inv_order_items(item_id),
  tax_code              TEXT NOT NULL,                 -- bản chụp lúc sinh file
  state                 TEXT NOT NULL
                        CHECK(state IN ('GENERATED','INVOICED','RELEASED','VOIDED')),
  invoice_no            TEXT,
  supersedes_invoice_no TEXT,                          -- neo sẵn, trống ở Phase 1
  voided_by             TEXT,
  void_reason           TEXT,
  CHECK (line_type <> 'VEHICLE' OR (vin IS NOT NULL AND engine_no IS NOT NULL)),
  CHECK (state <> 'VOIDED' OR (voided_by IS NOT NULL AND void_reason IS NOT NULL))
) STRICT;

CREATE UNIQUE INDEX ux_vin_active ON fin_batch_items(vin)
  WHERE state IN ('GENERATED','INVOICED');   -- AD-10

Luồng chính — nhận LXN mới

đăng tin LXN lên nhóm payload qua socket bóc chuỗi thô → JSON raw_model_text, raw_color_text, qty chuẩn hóa + tra alias (tất định) sku_id · hoặc CHẶN & hỏi lại (AD-13) BEGIN IMMEDIATE · ghi đơn · COMMIT tính lại tồn khả dụng (derived, AD-6) bảng 6 cột + nguồn gốc số liệu (AD-17) văn bản phản hồi bảng tồn khả dụng message_id ghi inv_bot_messages parse & chuẩn hóa NGOÀI transaction (AD-2) GĐKD Gateway (Node) app/ adapters/llm core/ kazuki.db
đăng tin LXN lên nhóm payload qua socket bóc chuỗi thô → JSON raw_model_text, raw_color_text, qty chuẩn hóa + tra alias (tất định) sku_id · hoặc CHẶN & hỏi lại (AD-13) BEGIN IMMEDIATE · ghi đơn · COMMIT tính lại tồn khả dụng (derived, AD-6) bảng 6 cột + nguồn gốc số liệu (AD-17) văn bản phản hồi bảng tồn khả dụng message_id ghi inv_bot_messages parse & chuẩn hóa NGOÀI transaction (AD-2) GĐKD Gateway (Node) app/ adapters/llm core/ kazuki.db

Luồng chính — nạp baseline AMIS buổi sáng

gửi file AMIS (PDF/Excel) parse bảng bằng pdfplumber (KHÔNG dùng LLM) dòng tồn TP/SX theo mã so tồn dự đoán hôm qua ↔ AMIS mới (AD-18) chênh lệch theo từng mã ghi baseline · is_day_open · effective_at (AD-7) xác định LXN chưa xuất phiếu cần carry-forward danh sách chờ GĐKD tick xác nhận báo Delta + danh sách LXN cần xác nhận Kế toán app/ adapters/pdf core/ kazuki.db Nhóm Telegram
gửi file AMIS (PDF/Excel) parse bảng bằng pdfplumber (KHÔNG dùng LLM) dòng tồn TP/SX theo mã so tồn dự đoán hôm qua ↔ AMIS mới (AD-18) chênh lệch theo từng mã ghi baseline · is_day_open · effective_at (AD-7) xác định LXN chưa xuất phiếu cần carry-forward danh sách chờ GĐKD tick xác nhận báo Delta + danh sách LXN cần xác nhận Kế toán app/ adapters/pdf core/ kazuki.db Nhóm Telegram

Cây mã nguồn

src/
  core/                  # hàm thuần — không I/O, không LLM, không đồng hồ
    availability.py      # AD-6 derived state, AD-7 carry-forward
    sku_matching.py      # AD-13 alias dài trước, ba kết cục
    reconcile.py         # AD-15 chuỗi kiểm tra 4 nguồn, AD-4 cặp vin+engine
    delta_report.py      # AD-18
  ports/                 # giao diện trừu tượng
    repository.py
    messenger.py
    file_source.py
  adapters/
    sqlite_repo.py       # AD-2 pragma, BEGIN IMMEDIATE
    telegram_relay.py    # AD-1 nói chuyện với Gateway qua socket
    pdf_parser.py        # pdfplumber
    excel_parser.py      # openpyxl, ép chuỗi cho vin/engine/mã hàng
    llm_normalizer.py    # AD-12 chỉ trả chuỗi thô
    chatlog_reader.py    # AD-16
  app/                   # điều phối use-case, transaction nằm ở đây
    ingest_baseline.py
    handle_lxn.py
    handle_query.py
    handle_cancel_edit.py
  migrations/            # .sql đánh số, ANSI, không dùng cú pháp riêng SQLite
  tests/
    golden/              # AD-20 — 17 đơn thật + negative assertions

Capability → Architecture Map

CapabilityLives inGoverned by
M01-CAP-1 Baseline AMIS đầu ngàyapp/ingest_baseline.py, adapters/pdf_parser.pyAD-7, AD-18, AD-2
M01-CAP-2 Chuẩn hóa & kiểm tra SKUcore/sku_matching.py, adapters/llm_normalizer.pyAD-12, AD-13, AD-14
M01-CAP-3 Tính & giữ tồn realtimecore/availability.pyAD-6, AD-7, AD-8
M01-CAP-4 Giao thức Bot & xác nhậnapp/handle_query.py, adapters/telegram_relay.pyAD-1, AD-17, AD-19
M01-CAP-5 Nhập-xuất thẳng (net = 0)core/availability.pyAD-6
M02-CAP-1 Đối soát 4 nguồncore/reconcile.pyAD-4, AD-15, AD-9
M02-CAP-2 Sinh file upload MISAadapters/excel_parser.pyAD-10, AD-11, AD-12
M02-CAP-3 Cảnh báo & khóa luồngapp/ + adapters/llm_normalizer.pyAD-12, AD-19
M02-CAP-4 Lưu vết tin nhắn Botinv_bot_messagesAD-1, AD-16
M02-CAP-5 Danh mục pháp nhâncore_customer_entitiesAD-5
M02-CAP-6 Vòng đời cờ hóa đơnfin_batch_itemsAD-8, AD-10

Deferred


[GIẢ ĐỊNH — cần soát] Toàn bộ namespace fin_ và các AD liên quan Module 02 chưa qua xác nhận của Chị Trang. Chúng được viết ở mức invariant để đáp án nào cũng khớp (xem AD-10, AD-11), nhưng vẫn là giả định cho tới khi có buổi phỏng vấn. Module 01 thì không có giả định nào chưa xác nhận.

Bản chiếu của kiến trúc theo bốn mức

Sơ đồ C4

Mức 1 — Bối cảnh hệ thống

Ai dùng, hệ thống nói chuyện với cái gì bên ngoài.

xuất báo cáo thủ công file Excel/PDF gửi qua Telegram file upload, người bấm 👤 GĐKD Trương Đức Anhphát LXN · tra cứu tồn 👤 Kế toángửi báo cáo AMIS · xuất hóa đơn 👤 Thủ kho TPxác nhận xe thực xuất 👤 NV Đăng kiểmgửi danh sách số khung 🤖 Kazuki AI AssistantTồn kho Realtime + Hóa đơn GTGT 📱 Telegramkênh giao tiếp duy nhất 🧾 MISA AMIS / meInvoicesổ sách kế toán — KHÔNG đấu API ở Phase 1
xuất báo cáo thủ công file Excel/PDF gửi qua Telegram file upload, người bấm 👤 GĐKD Trương Đức Anhphát LXN · tra cứu tồn 👤 Kế toángửi báo cáo AMIS · xuất hóa đơn 👤 Thủ kho TPxác nhận xe thực xuất 👤 NV Đăng kiểmgửi danh sách số khung 🤖 Kazuki AI AssistantTồn kho Realtime + Hóa đơn GTGT 📱 Telegramkênh giao tiếp duy nhất 🧾 MISA AMIS / meInvoicesổ sách kế toán — KHÔNG đấu API ở Phase 1

Điểm cần thấy: không có đường tự động nào giữa hệ thống và MISA. Dữ liệu đi qua file do Kế toán gửi tay. Đây là quyết định Phase 1, không phải hạn chế kỹ thuật.

Mức 2 — Khối thành phần

Hệ thống gồm những tiến trình nào, ai nói chuyện với ai.

Một máy chủ Python Engine · tiến trình ghi DUY NHẤT socket cục bộ 📱 Telegram Bot API ☁️ LLMchỉ bóc chuỗi & viết cảnh báo — AD-12 AI Gateway · Node.jsrelay thuầnKHÔNG chạm CSDL — AD-1 kazuki.db · SQLite WALcore_ · inv_ · fin_ Đĩa: file đính kèm+ file upload đã sinh app/ — điều phối use-caseranh giới transaction ở đây core/ — lõi tất địnhkhông I/O, không LLM adapters/ — sqlite · pdf · excel · llm
Một máy chủ Python Engine · tiến trình ghi DUY NHẤT socket cục bộ 📱 Telegram Bot API ☁️ LLMchỉ bóc chuỗi & viết cảnh báo — AD-12 AI Gateway · Node.jsrelay thuầnKHÔNG chạm CSDL — AD-1 kazuki.db · SQLite WALcore_ · inv_ · fin_ Đĩa: file đính kèm+ file upload đã sinh app/ — điều phối use-caseranh giới transaction ở đây core/ — lõi tất địnhkhông I/O, không LLM adapters/ — sqlite · pdf · excel · llm

Hai bất biến nhìn thấy được trên sơ đồ này: chỉ có một mũi tên vào CSDL, và LLM nằm ngoài lõi.

Mức 3 — Bên trong Python Engine

app/ — use-case core/ — hàm thuần, test không cần mock ports/ — giao diện adapters/ ingest_baselinenạp AMIS sáng handle_lxnnhận lệnh mới handle_querytra cứu tồn handle_cancel_edithủy / sửa lệnh availabilityAD-6 · AD-7 sku_matchingAD-13 · AD-14 reconcileAD-4 · AD-15 delta_reportAD-18 repository messenger file_source sqlite_repo telegram_relay pdf_parser excel_parser llm_normalizer chatlog_reader
app/ — use-case core/ — hàm thuần, test không cần mock ports/ — giao diện adapters/ ingest_baselinenạp AMIS sáng handle_lxnnhận lệnh mới handle_querytra cứu tồn handle_cancel_edithủy / sửa lệnh availabilityAD-6 · AD-7 sku_matchingAD-13 · AD-14 reconcileAD-4 · AD-15 delta_reportAD-18 repository messenger file_source sqlite_repo telegram_relay pdf_parser excel_parser llm_normalizer chatlog_reader

Quy tắc đọc sơ đồ: core/ không có mũi tên nào đi ra. Nếu lúc code thấy cần một mũi tên như vậy thì thiết kế đã sai chỗ nào đó, không phải quy tắc sai.

Mức 4 — Vòng đời một chiếc xe qua hai module

Không phải sơ đồ C4 chuẩn, nhưng đây là thứ khó nắm nhất khi đọc hệ thống.

Đăng kiểm cấp phiếu nhập core_vehicles có lệnh nhập kho LXN phát hành → trừ tồn khả dụng TP thiếu → giữ chỗ ở SX xe về được Kho TP Thủ kho xác nhận (vin + engine_no) GĐKD hủy lệnh → hoàn tồn mặc định GĐKD bật cờ cấp phép sinh file upload MISA Báo cáo 4a xác nhận upload lỗi → người nhả batch người hủy có lý do (AD-10) xuất lại cho pháp nhân khác DangKiem KhoSX KhoTP GiuCho GiuChoSX DaXuat ChoXuatHD DuocXuatHD Generated DaXuatHD Voided
Đăng kiểm cấp phiếu nhập core_vehicles có lệnh nhập kho LXN phát hành → trừ tồn khả dụng TP thiếu → giữ chỗ ở SX xe về được Kho TP Thủ kho xác nhận (vin + engine_no) GĐKD hủy lệnh → hoàn tồn mặc định GĐKD bật cờ cấp phép sinh file upload MISA Báo cáo 4a xác nhận upload lỗi → người nhả batch người hủy có lý do (AD-10) xuất lại cho pháp nhân khác DangKiem KhoSX KhoTP GiuCho GiuChoSX DaXuat ChoXuatHD DuocXuatHD Generated DaXuatHD Voided

Hai chỗ dễ hiểu sai: **GiuChoSX không tự hết hạn (AD-8), và Voided chỉ do người bấm**, không có đường tự động nào dẫn tới nó (AD-10).

Ước lượng công sức & cơ sở báo giá

Nội bộ — không gửi khách. Phần này chứa ước lượng ngày công và cách tiếp cận báo giá.

Chia việc theo Epic

Chia theo ranh giới kiến trúc, không theo màn hình hay tính năng, để mỗi epic có thể giao cho một người và hoàn thành độc lập. Đơn vị ước lượng là ngày công của một dev quen việc, chưa tính thời gian chờ khách phản hồi.

Mức tin cậy: 🟢 chắc (đủ dữ kiện, đã có mẫu thật) · 🟡 vừa (rõ nghiệp vụ, chưa có mẫu) · 🔴 thấp (chưa có xác nhận khách)


GIAI ĐOẠN A — Nền tảng (bắt buộc trước mọi thứ)

A1. Hạ tầng CSDL & bộ khung Hexagonal 🟢 — 2 ngày

Dựng kazuki.db theo schema trong spine, migration đánh số, lớp sqlite_repo với đủ pragma của AD-2, khung thư mục core/ports/adapters/app. Xong khi: chạy migration sạch trên máy trắng; test chứng minh foreign_keysSTRICT có hiệu lực; BEGIN IMMEDIATE được ép ở mọi đường ghi. Ràng buộc: AD-1, AD-2, AD-3

A2. Parser báo cáo AMIS (PDF + Excel) 🟢 — 2 ngày

pdfplumber bóc bảng tồn kho, ép chuỗi cho mọi cột mã, nạp inv_baselines + inv_baseline_stocks, idempotent khi nạp lại cùng file. Xong khi: parse đúng 100% cả 5 file PDF thật đang có; nạp lại lần hai không sinh thêm dòng. Rủi ro đã biết: đây chính là chỗ agent cũ đọc nhầm 5 thành 15 — bắt buộc có test khoá con số đó. Ràng buộc: AD-2, AD-21

A3. Từ điển SKU & pipeline khớp tất định 🟢 — 3 ngày

core_skus, core_sku_aliases, chuẩn hóa bằng luật cố định, khớp alias dài trước, ba kết cục, hàng đợi alias chưa giải được. Xong khi: 17 đơn thật khớp đúng; các negative assertion đều chặn (VCGTVC GT 1, ghi sầnghi đá). Ràng buộc: AD-13, AD-14, AD-21


GIAI ĐOẠN B — Module 01, phần demo được

B1. Engine tồn khả dụng (derived state) 🟢 — 3 ngày

Truy vấn tính tồn, phân bổ TP trước rồi SX, cờ HOLD_SX_PENDING, không kẹp âm. Xong khi: chạy lại 17 đơn thật ra đúng số anh Đức Anh đã tính tay. Ràng buộc: AD-6, AD-7, AD-8

B2. Vòng đời baseline & carry-forward 🟢 — 2 ngày

Reset mốc, kế thừa effective_at, carry-forward theo tình trạng xuất phiếu, danh sách chờ GĐKD tick xác nhận mỗi sáng. Ràng buộc: AD-7

B3. Adapter LLM bóc tách LXN 🟢 — 2 ngày

Trả JSON chuỗi thô, không sinh định danh, hợp đồng đầu ra chặt, null khi không chắc. Ràng buộc: AD-12

B4. Giao thức Bot: tra cứu, hủy, sửa 🟢 — 3 ngày

Bảng 6 cột theo đúng mẫu đã chốt, nguồn gốc số liệu inline, truy vết đơn nào đã lấy màu đó, hủy/sửa bằng reply tin gốc, lưu inv_bot_messages. Ràng buộc: AD-16, AD-17, AD-19

B5. Báo cáo Delta mỗi sáng 🟢 — 1 ngày

So tồn dự đoán hôm trước với AMIS mới, báo chênh lệch theo mã, kích hoạt theo sự kiện nhận file. Vì sao không cắt được: đây là thứ duy nhất biến cam kết "chính xác 100%" thành số đo được. Cũng là công cụ gỡ lỗi khi khách nói sai. Ràng buộc: AD-18

B6. Bộ test Golden-Master 🟢 — 2 ngày

17 đơn thật thành fixture, các đáp án sai thành negative assertion, harness replay từ chatlog. Ràng buộc: AD-20

Cộng Giai đoạn A + B: 20 ngày công. Đây là phạm vi demo cho anh Đức Anh và là Gói 1 báo giá.


GIAI ĐOẠN C — Nền dùng chung của Module 02

C1. Parser file Đăng kiểm 🟢 — 1,5 ngày

Đã có file mẫu thật và đã bóc được cấu trúc: model ở header, lấy cột "Màu xe (Thực tế)", lọc Kết quả = Đạt, khóa idempotency từ số báo cáo + lần. Ràng buộc: AD-21, AD-22

C2. Nguồn 3 — Thủ kho xác nhận số khung + số máy 🟡 — 3 ngày

Reply vào tin LXN, khớp cặp (vin, engine_no), ưu tiên cho chọn từ danh sách thay vì gõ tay. Lưu ý triển khai: đổi thói quen hàng ngày của Thủ kho — không đưa vào trước khi ký hợp đồng. Giá trị kép: đây cũng là tín hiệu xuất phiếu mà Module 01 đang thiếu, bỏ được việc GĐKD ghi tay mỗi sáng. Ràng buộc: AD-4, AD-23


GIAI ĐOẠN D — Module 02, phần còn lại

D1. Nạp 2 báo cáo MISA 🔴 — 2 ngày (chờ file mẫu)

D2. Engine đối soát 4 nguồn 🟡 — 4 ngày

Chuỗi kiểm tra tất định, dừng ở lỗi đầu, gồm ENGINE_MISMATCH. Ràng buộc: AD-4, AD-15

D3. Sinh file upload MISA 🔴 — 3 ngày *(chặn: thiếu template 6.UP MQC)*

D4. Máy trạng thái hóa đơn & batch 🟡 — 2 ngày

D5. Cảnh báo & khóa luồng 🟡 — 2 ngày

Cộng Giai đoạn C + D: 17,5 ngày công — nhưng D1 và D3 đang bị chặn, và toàn bộ D chưa có xác nhận nào từ chị Trang. Đây là Gói 2, chỉ nên báo giá sau khi phỏng vấn.


Đường găng & thứ tự làm

A1 Hạ tầng CSDL A2 Parser AMIS A3 Từ điển SKU B1 Engine tồn B2 Baseline & carry-forward B3 LLM bóc tách B4 Giao thức Bot B5 Delta sáng B6 Golden-Master C1 Parser Đăng kiểm C2 Thủ kho xác nhận D2 Đối soát 4 nguồn D1 Nạp MISA (chặn) D3 Sinh file (chặn) D4 Máy trạng thái
A1 Hạ tầng CSDL A2 Parser AMIS A3 Từ điển SKU B1 Engine tồn B2 Baseline & carry-forward B3 LLM bóc tách B4 Giao thức Bot B5 Delta sáng B6 Golden-Master C1 Parser Đăng kiểm C2 Thủ kho xác nhận D2 Đối soát 4 nguồn D1 Nạp MISA (chặn) D3 Sinh file (chặn) D4 Máy trạng thái

Đường găng của demo: A1 → A3 → B1 → B4. Rút ngắn được thì rút ở đây, không phải chỗ khác.


Ghi chú cho việc báo giá

Gói 1 (A + B, 20 ngày) — phạm vi rõ, dữ kiện đầy đủ, đã có dữ liệu thật để kiểm chứng. Báo giá cố định được, rủi ro vượt dự toán thấp.

Gói 2 (C + D, ~17,5 ngày) — con số này là ước lượng thô, chưa nên đưa vào hợp đồng. Hai hạng mục đang bị chặn vì thiếu file mẫu, và toàn bộ nghiệp vụ chưa qua xác nhận của người phụ trách. Nên chào một buổi khảo sát với chị Trang trước, rồi mới báo giá phần này.

Nên tách hai gói thay vì gộp một con số. Lý do nói thẳng được với khách: chưa phỏng vấn người phụ trách thì báo giá chỉ là đoán — đoán thiếu thì mình chịu, đoán thừa thì khách thấy đắt. Và demo Gói 1 thành công sẽ tự bán Gói 2, lúc đó đã có dữ liệu thật để báo đúng.

Chưa tính trong hai gói trên: thời gian chạy pilot song song 1–2 tuần (chỉ giám sát báo cáo Delta, không tốn ngày công phát triển), đào tạo người dùng, và vận hành sau bàn giao.