Javis OS
Javis OS는 기업가를 돕는 에이전시 AI로, 업무를 지원하고 함께 성장합니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
Offline-first warehouse stock management PWA. Tracks WSR / WSI / WTS / ESI / ESR
transactions, AI / SIA authority balances, and pile-level milled rice inventory.
This scaffold was generated without running npm install (no network access
in the build sandbox). To get running locally:
Then open the printed local URL — the dark theme should load with placeholder
routes for /login, /, /piles, /reports, and /settings.
Project status — Phase 6 complete + Phase 7 groundwork (data model v9)README 전문 보기 ↗Offline-first warehouse stock management PWA. Tracks WSR / WSI / WTS / ESI / ESR transactions, AI / SIA authority balances, and pile-level milled rice inventory.
This scaffold was generated without running npm install (no network access
in the build sandbox). To get running locally:
npm install
npm run dev
Then open the printed local URL — the dark theme should load with placeholder
routes for /login, /, /piles, /reports, and /settings.
/login, /, /piles, /reports, /settings placeholders<Toaster /> configured per Section 2.3 (no alert()/confirm())users, piles, transactions, authorities,
provinces, warehouses, varietyTypes, sackTypes, signatories,
reportConfig, transactionTypes, googleSheetsConfig
(Section 3.2 + revisions)src/services/firebase.js)src/services/syncWorker.js)src/pages/Login.jsx)db.users (src/context/AuthContext.jsx)/login if unauthenticated, supports requireRole (src/components/common/ProtectedRoute.jsx)/admin (admin-only) — tabbed config panel: Provinces,
Warehouses, Users, Varieties, Sack Types, Transaction Types, AI/SIA,
Signatories, Google Sheets (src/pages/AdminDashboard.jsx)src/context/WarehouseContext.jsx)src/components/layout/BottomNav.jsx)src/components/common/TransactionModal.jsx)src/pages/Home.jsx)src/pages/AdminHome.jsx)11760001) and a -/+ stepper, uniqueness
checked per document type (src/utils/serialNumber.js)authorities, on
WSI/WTS); Customer Name ("Name" placeholder), with RSBSA + Gender
fields appearing directly below it when Nature of Transaction is
Procurement; Nature of Transaction, Pile ID, Variety Type, and MTS
Sack Code+Condition (all sorted alphabetically, MTS auto-selects when
only one condition exists); Number of Bags, Gross Kilos; Net Kilos
auto/manual toggle (fixed alignment) + average weight per bag; Age
(value+unit); a required single Condition choice (GQ/TRD/INF/PD/TD);
a Procurement-only Farmers Organization toggle for the cooperative
case, revealing a multi-member section when switched on
(src/components/forms/StockFormBase.jsx)authorities); Customer Name; Nature of Transaction;
repeatable Sack Code + Condition + Pieces lines via "Add line"
(condition auto-selects when a sack code has only one configured)
(src/components/forms/SackFormBase.jsx)window.openTransactionForm (src/components/common/admin/AuthoritiesInfoPanel.jsx)src/components/common/ConfirmDialog.jsx)src/services/googleSheetsBridge.js) — one
Web App URL pointing at the user's EXISTING Apps Script (this app
does not generate or deploy a script of its own) fronting a
spreadsheet with separate named sheets for AI allocations, SIA
allocations, and FOUR backup logs split by document type (Receipts/
WSR, Issues/WSI, Sacks Receipts/ESR, Sacks Issues/ESI); pulls AI/SIA
data into db.authorities (AI keyed by its own aiNumber, SIA keyed
by its own, separate siaNumber — never shared) and pushes a backup
row per transaction once it syncs to Firestore, matching the user's
existing receipts sheet column-for-column for WSRsrc/components/common/admin/GoogleSheetsPanel.jsx)startAuthoritySyncWorker in src/services/syncWorker.js)Document-type split: WSR/WSI/WTS are "stock" documents (kilos + bags, tied to a Pile) using
StockFormBase; ESR/ESI are "sack" documents (pieces only, no pile, no kilos) usingSackFormBase. Both share onetransactionstable, but stock-only fields (e.g.pileId,grossKilos) and sack-only fields (e.g.sackLines) are simply left unset on documents of the other kind.
Note:
/adminis a new route added in Phase 3 (not in the original Section 1.3 list) so the Admin Dashboard from Step 3.3 is reachable. Admins see a link to it from the Settings page.
Admin | Warehouse Supervisor |
Warehouse Assistant (was admin | supervisor).supervisorInitials. They now have name (full
name, for reports/signatures) and nickname (shown in the welcome toast
and on the Home/warehouse page).users.assignedWarehouses is now an array of warehouses.warehouseId
values (multi-entry indexed as *assignedWarehouses) — one user can be
assigned to multiple warehouses. Admin users implicitly have access to
every warehouse regardless of this list (stored as []); the Users tab
hides the warehouse picker for Admins and shows a note instead.provinces — { provinceId, code, name }. A province can have multiple
warehouses.warehouses — { warehouseId, code, name, provinceId }. e.g. code
050501, name ALB-TABACO GID.varietyTypes — { varietyId, category, name }. category is Rice
or Palay; each category has its own set of varieties.sackTypes — { sackTypeId, category, code, weights: { BN, SH, US } }.
Every sack type has exactly three fixed conditions — Brand New,
Second Hand, Unserviceable — each with its own configurable
weight in kg, e.g. code PPRE50, category Palay, weights
{ BN: 0.095, SH: 0.102, US: 0.11 }. Weights are optional — any
condition can be left blank (stored as null) and filled in later.signatories — one record per warehouse (keyed by warehouseId),
storing only certifiedCorrectPosition. The "Certified Correct" name
is derived at render time from whichever user(s) have the
Warehouse Supervisor role for that warehouse — only the position
(e.g. "PCO/Acting WS") is configured per warehouse.reportConfig — a single global record (id: 'global') applied to
every report regardless of warehouse: verifiedCorrect (an array of
— supports multiple signatories),
/ , and / .The Signatories tab has two sections:
There is no "Prepared By" line — it was an earlier addition based on an ambiguous 5th line in the sample report image, and has been removed per clarification.
Phase 5 was substantially redesigned partway through after reviewing actual NFA "Empty Sacks Issue", "Empty Sack Receipt", "Warehouse Stock Issue", and "Warehouse Stock Receipt" sample documents. Key differences from the original Section 5 sketch:
StockFormBase. ESR/ESI are "sack"
documents — pieces only, no pile, no kilos — built on SackFormBase.
Both still share the one transactions table (same id/type/
serialNo/status/date/isSynced), but each only populates the
fields relevant to its own kind.B11766626, 0111965, 1729564 — not the
[Warehouse]-[Type]-[Initials]-[Counter] compound pattern originally
sketched in Section 3.1. The serial is now the first field on every
form: the system suggests a starting value (one higher than the highest
existing serial for that document type), but the user can freely
overtype it. A −/+ stepper next to the field nudges the trailing
numeric run by 1 (preserving any letter prefix) so a user working through
a physical pad of pre-numbered forms can navigate without retyping.
Uniqueness is enforced per document type only — the WSR pool, the WSI
pool, etc. — not scoped by warehouse. See src/utils/serialNumber.js.ageValue, : or
) rather than a single day count —
converts it for any aggregate/age-bucket logic (e.g. Admin Home),
treating 1 month as 30 days.A follow-up round of fixes once the v5 forms were reviewed against the real sample documents more closely:
db.authorities as before. Both fields are now positioned directly
below Date rather than near the bottom of the form, and StockFormBase
derives which label/behavior to use from the document type rather than
a prop.11760001)
as the fallback instead of 1, matching real NFA serial ranges. If a
prior document of that type already exists, the suggestion is still one
higher than the highest existing serial, same as before.e.currentTarget.showPicker?.() on click.PPMG50 only has a Brand New weight configured, picking
PPMG50 immediately selects "PPMG50 - BN" instead of requiring the user
to open a second dropdown with one option in it. Same behavior on the
ESR/ESI sack-line rows.farmerRsbsa/farmerGender, null on non-Procurement transactions).StockFormBase and
SackFormBase, matching the destructive/dismissive nature of closing
without saving..env.example to .env.local and fill in your project's config values
(Project Settings → General → Your apps → SDK setup and configuration).With npm run dev running, open the browser console. src/utils/devTools.js
exposes helpers (dev builds only):
// 1. Seed a mock transaction (isSynced: false) into local Dexie
await seedMockTransaction()
// 2. Confirm it persisted locally
await db.transactions.toArray()
// 3. In DevTools → Network, set throttling to "Offline", then seed another
// record — it will persist locally but stay isSynced: false.
await seedMockTransaction()
// 4. Switch back to "Online" (or "No throttling"). The sync worker listens
// for the `online` event and runs automatically, or trigger it manually:
await processSyncQueue()
// 5. Re-check — both records should now show isSynced: true, and the
// matching documents should appear in your Firestore `transactions`
// collection.
await db.transactions.toArray()
Schema changed to v4 — if you're updating from an earlier version, reset your local database first: open DevTools → Application → IndexedDB → right-click BSMDatabase → Delete database → refresh the page. Otherwise old records may have stale field names/values (e.g. lowercase roles) that no longer match the new checks.
"Type an invalid PIN... must throw a toast warning. Enter a valid registered PIN... must direct you to the home dashboard."
npm run dev running, the app should load straight to /login
(you'll be redirected here automatically — there's no logged-in user yet).000000). You
should see a red "Invalid access PIN" toast, and the dots reset.users, provinces, and warehouses
tables start empty, open the browser console and run:
await seedAdminUser()
This creates province ALB (Albay), warehouse 050501 (ALB-TABACO GID),
and an admin account with PIN 123456, name "Juan Santos", nickname "Juan".1 2 3 4 5 6 on the keypad. You should see a green
"Welcome, Juan" toast and land on the Home page (/), which greets you
as "Welcome back, Juan."Juan · Admin, Juan Santos, All Warehouses),
a link to Admin Dashboard, and a Logout button./admin, you'll see tabs: Provinces,
Warehouses, Users, Varieties, Sack Types, AI / SIA, Signatories.
CAM, name
"Camarines Sur"). Try editing it, then deleting it.050502,
name "CAM-PILI GID", province CAM). Edit and delete should work.654321, role Warehouse Supervisor,
name "Maria Reyes", nickname "Maria", and check the box for warehouse
050501. It should appear in the list showing 050501 as its access.
Try editing it (e.g. check a second warehouse too), then deleting it
(re-add it afterward since the next steps use it).Rice (e.g. "NSIC Rc 222") and one
under Palay (e.g. "NSIC Rc 480"). They should appear grouped by
category.Palay, code PPRE50 —
and fill in only the BN weight field (), leaving SH and US
blank. Save — the list should show .
Edit it later to fill in the rest."Tap the central interactive action button; the bottom-sheet flyout interface menu must open smoothly. Tap outside the modal element space boundaries or click the Cancel option button; the layout overlay must hide instantly."
123456 for Admin, or 654321 for the supervisor seeded in
Phase 3). You should land on Home with the bottom navigation bar
visible — Home, Piles, a green circular + button in the center,
Reports, and Settings.050501:
await seedMockPiles()
Refresh the page. Home should now show two summary cards — Total
Bags (950, the sum of currentBags across the three seeded piles) and
Total Net Bags (0, since no transactions exist yet) — followed by a
list of the three piles (Pile A-1, Pile A-2, Pile B-1) each showing their
cereal type, bag count, and age in days.const piles = await db.piles.toArray()
await seedMockTransaction({ pileId: piles[0].pileId, status: 'Active', numberOfBags: 100 })
Refresh the page — Total Net Bags should now read 100.050502 earlier), a
dropdown should appear above the summary cards letting you switch the
"currently selected facility" — switching it should update the piles
shown (it'll be empty for any warehouse other than 050501 until you
seed piles there too).A note on the summary cards: "Total Bags" is the sum of
piles.currentBagsfor the selected warehouse (the "total counted volume metric"). "Total Net Bags" is the sum ofnumberOfBagsfrom that warehouse's Active transactions — the bags actually recorded via WSR/WSI/WTS forms, used here as the "overall inventory metric". It'll read 0 until transactions are created. Per-pile age (Section 4.2) is still shown on each pile card. Piles themselves don't yet have an admin/UI flow to create or edit them (that's Phase 7), hence theseedMockPiles()dev helper for now.
Schema changed to v5 — reset your local database first: DevTools → Application → IndexedDB → delete BSMDatabase → refresh, then run
await seedAdminUser()again.
await seedFormConfig() // Transaction Types, Varieties (WD1/PD), Sack Types (PPMG50/PPRE50)
await seedMockPiles() // sample piles in warehouse 050501
await seedMockAuthority() // one sample AI + one sample SIA
11760001 and have
−/+ buttons beside it — tap + a couple of times and confirm
the number increments by 1 each time. Tap anywhere in the Date field
(not just the calendar icon) and confirm the native date picker opens.
Directly below Date, confirm there's a WSI No. field (not "AI
No.") — leave it blank, it's optional on WSR. Confirm the Customer Name
field's placeholder just says "Name". Open the Nature of Transaction,
Pile ID, and Variety Type dropdowns and confirm their options are
alphabetically sorted; pick "Milling", a Pile, and variety PD.
Under "MTS — Sack Code & Condition", confirm only PPRE50 options
appear (Palay-only, since PD is a Palay variety) — pick PPRE50 - BN.
Enter 100 bags and 5050 Gross Kilos. Net Kilos should auto-show
5040.50 (100 × 0.095 = 9.5 MTS, 5050 − 9.5), with "Average weight per
bag: 50.41 kg" beneath it. Confirm the "Auto-compute Net Kilos" toggle
knob sits neatly inside its track (not overflowing) — toggle it off,
confirm the field becomes editable, then back on to confirm it
recalculates. Try tapping Save without picking a Condition — it
should be blocked with an error toast; pick a Condition (e.g. GQ) and
Save again.One Apps Script Web App URL — the user's EXISTING script, not one this app deploys — fronts a single spreadsheet containing six named sheets (the names are all admin-configurable, defaults shown):
authorities tableDATA_ENTRY, matching the
user's existing receipts sheet name and column shape exactly (see below)Configure the URL and every sheet name in the Admin Dashboard's Google Sheets tab.
This app does not generate, deploy, or ship its own Apps Script. The Google Sheet already has its own Apps Script running on it with functions for other purposes, and that script must not be touched, overwritten, or replaced.
src/services/googleSheetsBridge.jsonly calls whatever Web App URL is configured — it describes the contract this app needs, so the script's maintainer can add matching functions alongside whatever already exists, rather than this app prescribing a full script to paste in.
The user's existing receipts sheet (sample name DATA_ENTRY) has these
columns, in this order:
Timestamp | Date | Transaction | Variety | Bags | Net Kilos |
Warehouse Name | Customer Name | Province | Net Bags | WH Code | WSR # |
Batch No | Col_15
A few things worth calling out, confirmed from a real data sample:
Timestamp is when the row was actually written (server time);
Date is the date typed into the form — these can differ, and both
are preserved.Net Bags = Net Kilos / 50, matching calculateNetBags() in
src/utils/calculations.js exactly — even for Procurement/Palay rows,
where the math still checks out (e.g. 6204.025 kg / 50 = 124.0805).Col_15 is the age column, but it's only ever populated when age was
entered in MONTHS. A blank Col_15 means the original entry's age was
in days (and isn't recorded in this column at all); a value like 8
means "8 months old". This app's pushTransactionBackup() reproduces
this exact behavior: Col_15 is set to transaction.ageValue only when
transaction.ageUnit === 'Months', and left blank otherwise.WH Code and Province are the warehouse's facility code and
province code, not their full names — Warehouse Name carries the full
name separately.pushTransactionBackup()'s WSR row shape matches every one of these
columns exactly, so it can append directly into the user's real
DATA_ENTRY sheet (or whatever it's renamed to in the admin config)
without any restructuring on the sheet side.
The user doesn't yet have sheets for WSI (issues), or for ESR/ESI (sack receipts/issues) — three new tabs need to be added to the spreadsheet, named whatever is configured in the admin tab's corresponding fields:
WSR # is replaced with AI # (an issuance is linked to an
Authority to Issue, not a prior WSI), and there's no Batch No
equivalent (that was specific to milling receipts).Timestamp | Date | Transaction | Warehouse Name | Customer Name |
Province | WH Code | Pieces | ESI # (on ESR) or SIA # (on ESI)
The exact contract this app expects (see the full header comment in
src/services/googleSheetsBridge.js for the canonical version, including
buildBackupRow() which builds each shape above):
GET {webAppUrl}?action=fetchAuthorities&sheet={sheetName}&type=AI
-> { status: "SUCCESS", rows: [ { aiNumber, assignedWarehouseCode,
customerName, varietyName, totalAllocationBags,
totalAllocationKilos }, ... ] }
GET {webAppUrl}?action=fetchAuthorities&sheet={sheetName}&type=SIA
-> { status: "SUCCESS", rows: [ { siaNumber, assignedWarehouseCode,
customerName, sackCode, condition, totalAllocationBags,
totalAllocationKilos }, ... ] }
POST {webAppUrl} body: { action: "appendTransaction", sheet: sheetName,
row: { ...one of the four shapes above, depending on document type... } }
-> { status: "SUCCESS" }
If the existing script uses different action names, parameter names, or
response shapes than the above, the request-building/response-parsing in
googleSheetsBridge.js is the side that should change to match it — not
the other way around.
This is the full path a transaction takes, so it's clear nothing touches the Sheet except through one narrow, well-tested function:
transactions table immediately (works fully offline), with
isSynced: false.processSyncQueue() (Phase 2's existing sync engine, runs on load and
whenever connectivity returns) finds that record and setDocs it to
Firestore. Firestore is the primary cloud store — this step alone
is what makes the data durable in the cloud.syncWorker.js makes one
best-effort call to pushTransactionBackup(tx, context), which looks
up the transaction's pile/warehouse/province/variety/transaction-type
names, builds the row shape for that document's type via
buildBackupRow(), and does a single fetch(..., { method: 'POST' })
to the configured Web App URL with { action: 'appendTransaction', sheet: <the right sheet name>, row: {...} }.fetchAuthorities GET (to read
AI/SIA rows) — there's no delete, no update-in-place, no formatting
change, no script-modification call anywhere in the codebase. The
existing script's other functions are never invoked or referenced.Pulling AI/SIA data: syncAuthoritiesFromSheets() fetches both the AI
and SIA sheets in one pass and upserts matching records into
db.authorities — AI rows matched/keyed by aiNumber, SIA rows
matched/keyed by siaNumber. Existing totalIssuedBags/totalIssuedKilos
are preserved across re-syncs, since issuance is tracked locally as
WSI/ESI forms are saved, not re-derived from the Sheet. This runs three
ways: once immediately when a user logs in, on a 5-minute interval in the
background (startAuthoritySyncWorker in src/services/syncWorker.js),
and again whenever the browser regains connectivity — plus a manual "Sync
Now" / refresh button in both the Google Sheets tab and the AI/SIA tab
itself.
All bridge calls degrade gracefully: if no URL is configured, or the
device is offline, or the script returns something unexpected, the
relevant function returns a typed { ok: false, reason } rather than
throwing — important since this can run unattended in the background.
Schema changed to v8 — reset your local database first: DevTools → Application → IndexedDB → delete BSMDatabase → refresh, then run
await seedAdminUser()again.
Without changes to the existing Apps Script, you can still verify the UI plumbing:
/admin → Google Sheets tab. Enter any syntactically valid
URL (e.g. https://example.com/test), leave every sheet name at its
default, and tap Save — you should see a success toast, and the
"Last synced" line should still read "Never" (saving config doesn't
trigger a sync by itself).https://example.com/test doesn't implement
the expected endpoints, you should see a "Sync failed" error toast
(this confirms the request path runs and fails gracefully rather than
crashing the page).fetchAuthorities/appendTransaction
actions described above, configure the real Web App URL, add a row to
the AI sheet with a warehouse code that matches one of your configured
warehouses (e.g. 050501) and a variety name that matches one of your
configured varieties (e.g. PD), then tap Sync Now. You should see
a success toast like "Synced 1 AI and 0 SIA record(s)", and the new
allocation should appear in the AI/SIA monitor on Home, tappable into a
pre-filled WSI form exactly like the seedMockAuthority()-created ones.DATA_ENTRY sheet — a new row should appear
matching the columns described above, including a blank Col_15 if you
entered the age in Days, or a number if you entered it in Months.A round of fixes once real usage surfaced several Phase 5/6 gaps: piles never actually updated when transactions saved against them, Home's totals used the wrong formula, AI/SIA had no user-facing home, and there was no customer memory across forms. Schema bumped to v8.
piles gains varietyId (kept alongside the existing cerealType —
both matter, neither replaces the other) and currentKilos (alongside
currentBags). src/utils/pileLedger.js exports
applyTransactionToPile(), called right after every WSR/WSI save:
WSR adds numberOfBags/netKilos to the pile's running totals; WSI
subtracts them. WTS does not yet move pile totals — per
clarification, WTS is actually a distinct "in-warehouse" document with
its own receipt AND issue sides in one document (both with their own
bags/kilos), customer being the warehouse supervisor — that hasn't been
designed or built yet, and is flagged as outstanding work below, not
silently faked with the current single-sided form.StockFormBase.jsx) — since a pile can never mix varieties, the
Variety Type field becomes read-only the moment a Pile is chosen,
showing that pile's actual variety.src/components/forms/NewPileDialog.jsx, an inline sub-form (Pile Name,
Category, Variety) that creates the pile on the spot, adds it to
db.piles, and auto-selects it back on the parent transaction form —
no need to leave the WSR/WSI/WTS form to set up a pile that doesn't
exist yet.src/components/forms/CustomerNameAutocomplete.jsx
src/utils/customerDirectory.js) — once 3+ characters are typed, up
to 6 matching names are suggested from a customers directory built
automatically as forms are saved. Picking a suggestion (or typing an
exact existing name) auto-fills RSBSA + Gender if on record, and
auto-enables the Farmers Organization toggle (with its saved member
list) if that name was previously recorded as a cooperative — the app
remembers, per name, whether it's an individual or a coop, exactly as
requested.scrollToCustomerName() in StockFormBase.jsx, using a
forwardRef-exposed .focus() on the autocomplete component).SackFormBase.jsx lacked customer-autocompleteA second round of fixes, all from live testing feedback. Schema bumped to v9.
transactions gains an indexed warehouseId field (previously only
reachable indirectly via pileId, and sack documents had no warehouse
link at all). src/utils/serialNumber.js was rewritten so
suggestNextSerial, isSerialTaken, and the new findTransactionBySerial
are all scoped to (type, warehouseId) — switching the warehouse on a
form now correctly switches which serial pool applies, instead of
treating every warehouse's WSR/WSI/WTS/ESR/ESI numbers as one shared
pool (which could both falsely block a legitimate serial and falsely
allow a duplicate across warehouses).StockFormBase.jsx
and SackFormBase.jsx now have a < / > stepper around the serial
field (replacing the old -/+). Stepping to, or directly typing, a
serial that already has an Active document for the current (type, warehouse) loads that document's full data into the form and swaps the
footer from Save to Update (green) and Delete (red):
ConfirmDialog (never a native confirm()), then removes the
transaction entirely — freeing that serial number for a fresh entry,
exactly as requested.src/utils/pileLedger.js gained reverseTransactionFromPile(), the
inverse of applyTransactionToPile(), used by both Update and Delete.src/pages/AdminMonitoring.jsx) replaces the
Piles tab for Admins specifically — an admin has no warehouse of their
own, so a piles list was never useful to them. BottomNav.jsx is now
role-aware: Admins see "Monitor" (Radar icon, /monitoring), everyone
else still sees "Piles". The Monitoring page has AI/SIA tabs showing
every authority across every warehouse — full Milling/Remilling/Test
Milling monitoring is planned but explicitly NOT built yet.AuthorityMonitor.jsx rewritten) —
AI and SIA are now separate top-level tabs, each with its own
Pending/Completed sub-tabs. A checkbox on the left of each row lets the
user manually mark an allocation "Completed" even if it isn't fully
issued against (some AI/SIA don't need full documentation to be done);
unchecking it from the Completed tab moves it back to Pending. This is
tracked via the new field, independent
of the balance-derived Pending/Complete/Over-Issued status.StockFormBase) with a receipt side and an issue side, each with
their own bags/kilos, and the warehouse supervisor as the implicit
customer. Until it exists, WTS transactions save normally but do not
move any pile's running totals — this has come up consistently across
sessions and remains intentionally deferred."4 BN / 144 US / 876 SH / 6014 BN / 193 US" into separate sack-type groups by detecting which
condition code repeats) are deferred until real Google Sheet connection
testing is underway, per explicit instruction — rawSiaAllocation is
stored as-is in the meantime and shown verbatim in both monitoring
views.Schema changed to v9 — reset your local database first: DevTools → Application → IndexedDB → delete BSMDatabase → refresh, then run
await seedAdminUser()again.
await seedFormConfig() then await seedMockPiles() then
await seedMockAuthority()./monitoring shows AI/SIA tabs with the seeded
records.< on the serial field to step back to the one you just saved —
confirm the form loads that document's full data and the footer shows
Update (green) / Delete (red) instead of Save. Change the
Gross Kilos and tap Update — confirm the pile's totals reflect only
the new value (not double-counted). Tap < again, then Delete —
confirm a confirmation dialog appears, and after confirming, the pile's
totals reverse correctly and that serial is free again."{bags} net bags" on the left and
"{kilos} kg ({avg})" on the right of the second row, both to 2
decimals, no extra label text.WS in any Customer Name field — confirm Warehouse
Supervisor users are suggested (if any are seeded) instead of the
regular customer directory, and selecting one fills in that
supervisor's warehouse address.</> series navigation
works.A third round, fixing several things that only showed up under real use.
/monitoring; regular users now get the same content on the existing
/piles route (Piles.jsx now renders AuthorityMonitor, replacing
its old unbuilt-spatial-map stub). BottomNav's second tab is "Monitor"
for everyone now, pointing at /monitoring for Admins and /piles for
everyone else.suggestNextSerial's fallback changed from an arbitrary
'11760001' constant to '1' — a brand-new warehouse with no prior
documents of a given type starts that series at 1, not inheriting a
number that implied continuity with some other warehouse's range.EditPileAgeDialog: it only ever accepted a number of days, so
entering "7" meant 7 days, not 7 months — which is why a 7-month-old
Palay pile was landing in the "0–6 months" bucket on Admin Home. The
dialog now has a Days/Months unit selector, exactly like every
transaction form's Age field, and converts via normalizeAgeToDays
before storing — initialAgeValue on a pile is now reliably stored in
real days. (A normal WSR's Age field still does NOT touch the pile's
age — that remains exclusively the Edit Age dialog's job, per the
original clarification — this was confirmed working correctly already.)searchWarehouseSupervisors() returns e.g.
"WS John Jones — ABACORP A" and "WS John Jones — ABACORP B" as distinct
picks (only when the supervisor has 2+ assignments — a single-warehouse
supervisor still shows as one plain row), each carrying only that one
warehouse's address, so the right address always gets written.AuthorityMonitor.jsx (user) and AdminMonitoring.jsx
(admin) display the AI's linked variety name/category, and the SIA's
(plus the raw allocation string, e.g. "PPRE50 — 65 bn / 17
sh") as a subtext line.No schema version change this round — v9 remains current. A fresh reseed is still recommended to pick up the corrected
seedMockAuthorityfields:await seedMockAuthority()again is enough, no full database reset required.
/piles showing the AI/SIA monitor; for
an Admin it should be /monitoring.1, not a large placeholder number.12978.00 with Auto-compute Net
Kilos switched OFF (not Gross Kilos prefilled). Confirm the Pile ID
dropdown only lists piles matching the AI's variety.7 with the unit set to
Months, save — go to Admin Home and confirm that pile now appears
under the Palay "6.1–12 months" bucket, not "0–6 months".src/
├── assets/
├── components/
│ ├── common/
│ │ ├── ProtectedRoute.jsx
│ │ ├── ConfirmDialog.jsx
│ │ ├── TransactionModal.jsx
│ │ ├── AuthorityMonitor.jsx # tabbed AI/SIA monitor on Home, per-user scoped
│ │ ├── PileActionSheet.jsx # WSR/WSI/WTS popover anchored to a pile card
│ │ ├── EditPileAgeDialog.jsx # direct pile age override
│ │ └── admin/
│ │ ├── shared.js
│ │ ├── ProvincesPanel.jsx
│ │ ├── WarehousesPanel.jsx # warehouses now carry an address
│ │ ├── UsersPanel.jsx
│ │ ├── VarietyTypesPanel.jsx
│ │ ├── SackTypesPanel.jsx
│ │ ├── TransactionTypesPanel.jsx
│ │ ├── AuthoritiesInfoPanel.jsx
│ │ ├── SignatoriesPanel.jsx
│ │ └── GoogleSheetsPanel.jsx
│ ├── layout/
│ │ └── BottomNav.jsx # role-aware: Admin sees Monitor, others see Piles
│ ├── forms/
│ │ ├── shared.js
│ │ ├── SerialNumberField.jsx
│ │ ├── CustomerNameAutocomplete.jsx # autocomplete + WS-supervisor suggestions
│ │ ├── NewPileDialog.jsx # inline "New Pile" creation sub-form
│ │ ├── StockFormBase.jsx # WSR/WSI/WTS — warehouse-scoped serials,
│ │ │ # series back-nav w/ Update/Delete, stock caps
│ │ ├── SackFormBase.jsx # ESR/ESI — same treatment as StockFormBase
│ │ ├── WSRForm.jsx
│ │ ├── WSIForm.jsx
│ │ ├── WTSForm.jsx
│ │ ├── ESIForm.jsx
│ │ └── ESRForm.jsx
│ └── cards/ # BinCard, DailySummaryCard
├── context/
│ ├── AuthContext.jsx
│ ├── SettingsContext.jsx
│ └── WarehouseContext.jsx
├── db/
│ └── dexie.js
├── pages/
│ ├── Login.jsx
│ ├── Home.jsx
│ ├── AdminHome.jsx
│ ├── AdminMonitoring.jsx # Admin-only cross-warehouse AI/SIA monitor
│ ├── Piles.jsx # regular-user AI/SIA monitor (was an unbuilt spatial map stub)
│ ├── Reports.jsx
│ ├── Settings.jsx
│ └── AdminDashboard.jsx
├── services/
│ ├── firebase.js
│ ├── syncWorker.js
│ └── googleSheetsBridge.js # calls the EXISTING Apps Script Web App —
│ # see "Phase 6 — Google Sheets bridge" above
└── utils/
├── calculations.js # Net Kilos / MTS / Avg weight per bag / Age / Authority status
├── serialNumber.js # Serial suggest/step/uniqueness, scoped per (type, warehouseId)
├── pileLedger.js # applyTransactionToPile() / reverseTransactionFromPile()
├── customerDirectory.js # autocomplete + WS-supervisor suggestions + address
├── siaParsing.js # resolves SIA sack-type/condition/pieces for ESI prefill
└── devTools.js # Dev-only console helpers for testing
See .env.example and the Firebase setup section above.
{ name, position }auditedByNameauditedByPositionnotedByNamenotedByPositionWD1 for
Rice, PD for Palay.authorities.referenceNumber field is renamed to aiNumber to match the
Sheet's "AI Number" column. The Admin Dashboard's "AI / SIA" tab is now a
read-only info panel.ageUnitDaysMonthsnormalizeAgeToDays()condition: one of GQ,
TRD, INF, PD, TD) on stock documents only — sack documents
(ESR/ESI) don't carry a condition field. The user must pick one before
saving.farmerCoops is an array.transactionTypes table — { transactionTypeId, name } — the
admin-configurable "Nature of Transaction" list (Milling, Procurement,
Transfer, Sales, etc.), independent of the WSR/WSI/WTS/ESI/ESR document
type. Configured in the Admin Dashboard's new "Transaction Types" tab.sackLines is an array of
{ sackTypeId, condition, pieces }, and the user can add as many rows as
needed via "Add line". ESR carries a linkedDocNo referencing its ESI
No.; ESI's linkedDocNo doubles as its SIA No. (stored in the indexed
siaNumber field too).authorities now supports bags-only,
kilos-only, or both (totalAllocationBags/totalAllocationKilos,
totalIssuedBags/totalIssuedKilos) since real allocations aren't
always defined in both units. Tapping an AI or SIA row in the Admin
Dashboard's AI/SIA tab calls window.openTransactionForm(type, prefill)
— a small global hook App.jsx exposes — opening the matching WSI/ESI
form with name, variety/sack, bags, and kilos pre-filled. Saving a WSI
with an AI No. (or an ESI with a SIA No.) deducts from that authority's
balance even if the user typed the number manually instead of using the
deep link, since per clarification the net kilos/pieces actually issued
is what matters, not how the form was reached.0.095BN: 0.095kg · SH: —kg · US: —kg050501 — its Name field should show "Maria Reyes" (from the
Warehouse Supervisor you just created in the Users tab). Set its
Position to PCO/Acting WS and tap Save on that row. Under
"Verified Correct, Audited By, Noted By", add one or two entries to
Verified Correct (e.g. "Geraldine Ballete" / "Accountant III"), fill in
Audited By (e.g. "Bernadeth Añonuevo" / "State Auditor III") and Noted
By (e.g. "Al Martin Menes" / "Acting Branch Manager"), then tap the
bottom Save. Refresh the page and confirm everything reloads
correctly./login. Log in with the new supervisor PIN (654321).
You should reach Home greeting "Maria", but visiting /admin directly
should redirect you back to / since that account's role is
Warehouse Supervisor, not Admin./admin → AI / SIA tab. You should see the
seeded AI (26219637) and SIA (0111965) rows with their remaining
bags/kilos. Tap the AI row — it should close the admin dashboard view
and open a WSI form directly, with AI No. 26219637, Customer Name,
variety, Number of Bags (250), and Gross Kilos (12978) all
pre-filled. Pick a Condition and Save, then go back to the AI/SIA
tab — the AI's remaining balance should have decreased.PPMG50 with only a BN weight), confirm its
Condition dropdown auto-fills without you having to open it. Enter a
pieces count, then tap "Add line" (not "Add sack line") to add a second
row with a different sack code/condition. Confirm "Total pieces"
updates to the sum. Enter the SIA No. (0111965) and tap the single
full-width Save button at the bottom. Check the AI/SIA tab again —
the SIA's remaining pieces should have decreased.(sum of every pile's currentKilos in this warehouse) / 50currentBagssrc/components/common/AuthorityMonitor.jsx)
— previously AI/SIA only existed inside the Admin Dashboard's AI/SIA
tab, which is Admin-only by route and therefore invisible to the
Warehouse Supervisors/Assistants who actually need it. It now also
appears on Home, above the Piles list, scoped to ONLY the warehouses
the logged-in user can access (WarehouseContext.accessibleWarehouses).
Tapping a row auto-switches the selected warehouse first if needed
(setCurrentWarehouseId), then deep-links into a pre-filled WSI (AI) or
ESI (SIA) form, same as the admin panel's existing behavior. AI rows
show Customer Name + Nature of Transaction; SIA rows show the same, and
critically use "pieces" as the unit label instead of "bags" (sacks
are counted as pieces) — this was also fixed in the Admin Dashboard's
AI/SIA tab itself.seedMockPiles()) now shows up immediately in every Admin
Home table, even with zero transactions recorded. Net Bags is rounded to
exactly 2 decimals everywhere on the page.seedMockPiles() now sets realistic currentKilos and links each
seeded pile to a real varietyId (via seedFormConfig()'s WD1/PD
records), so the live-totals math has something correct to show
immediately after seeding.authorities.manuallyCompletedStockFormBase.jsx and SackFormBase.jsx both show a warehouse
selector in the header whenever more than one warehouse is accessible
(which for Admins means every warehouse) — switching it resets
Pile/Variety/MTS selection and exits any in-progress series edit, since
those are all warehouse-scoped. The selector is now on its own row below
the title, not crammed next to the close button.seedMockPiles() is
self-sufficient: if WD1/PD variety records don't exist yet, it
creates minimal versions itself instead of silently seeding a pile with
a blank varietyId (which broke the pile's variety lock).applyTransactionToPile() never
touched a pile's initialAgeValue/dateOfReceipt at all — only
currentBags/currentKilos — so entering "1 day" on a WSR had no way
to reach the pile's displayed age. Per clarification, pile age is now
treated as its own directly-editable property (not auto-derived from
whatever a WSR's age field says), with a dedicated Edit Age dialog
(EditPileAgeDialog.jsx, opened by tapping the age on any pile card) —
"disregarding everything else," since a separate department sometimes
handles age determination."1,509.24 kg (52.04)" with no extra
label text, both to 2 decimals.PileActionSheet.jsx is no longer a
bottom sheet (which hid WTS below the nav bar) — it's now a compact
popover anchored directly beneath the tapped pile card, three buttons
(WSR/WSI/WTS), no explanatory subtext.SackFormBase.jsx
(ESR/ESI) had fallen behind StockFormBase.jsx with a stale,
unscoped serial implementation — it's been fully rewritten to match:
same CustomerNameAutocomplete, same address field, same warehouse-
scoped serial logic, same series back-navigation with Update/Delete.db.users for Warehouse Supervisors instead of the regular customer
directory (searchWarehouseSupervisors() in customerDirectory.js),
suggesting "WS {name}" / "Acting WS {name}". Selecting one
auto-fills Address from that supervisor's assigned warehouse(s) —
joined with "; " if assigned to more than one. This required adding
address to the warehouses table and to the Admin Dashboard's
Warehouses panel.sackTypeRawsrc/utils/siaParsing.js resolves a SIA's
sackTypeRaw/rawSiaAllocation into real { sackTypeId, condition, pieces } lines for the common single-sack-type case (matching the
sheet's "PPRE50" + "65 bn / 17 sh" shape exactly). True multi-sack-
type allocations ("PPRE50/PPMG50" with the duplicate-condition-code
split) are still explicitly deferred per earlier instruction — when
unresolvable, the ESI form shows the raw allocation string as a banner
instead of silently leaving the form blank with no explanation.250/250 either way, just meaning different
things depending on which number was "remaining"). It now always shows
issued / total as a large, color-coded headline figure (white while
pending, neon green at exactly Complete, crimson if Over-Issued)
instead of a small subtext line with a separate "Pending" label — the
number itself communicates status now, so the redundant text label was
removed.