fix: resolve table column shift, normalize units and standardize dates

This commit is contained in:
Rafhan Mazaya Fathurrahman committed 2026-07-03 16:04:57 +07:00
1 parent 095d4b799a
commit 2900eb670b
46 files changed
+11904 -2747

No files matched your search

+13
View File
@@ -0,0 +1,13 @@
# Workspace Rules for PFM OCR App
## OCR Post-Processing & Parsing Rules
### 1. Table Column Shift Alignment Correction
When parsing OCR tables (e.g., columns like SKU, description, quantity):
- If the first row of a column is empty but has description/quantities in other columns, and a downstream row has a valid value in that column but is empty in other columns, this indicates a column shift.
- Scan for the shift boundary row.
- Shift the column values up by exactly one row to restore correct cell alignments.
- Clean the shifted boundary row value to prevent orphan rows from polluting the parsed output.
### 2. Date Normalization
- Always ensure single digit days are normalized with a leading zero (e.g., `7 June` to `07 June`, `4 April` to `04 April`) in post-processing.
+451
View File
@@ -0,0 +1,451 @@
import json
import os
import pandas as pd
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
def clean_val(val):
if val is None:
return ""
s = str(val).strip().upper()
if s in ["N/A", "NOT FOUND", "NOTFOUND", "EMPTY", "NONE", "-", "N / A"]:
return ""
s = " ".join(s.split())
s = s.replace("PT. ", "PT.")
s = s.replace("✓", "").replace("✔", "").strip()
return s
def main():
ai_file = "sources/ai_results.json"
manual_file = "sources/manual_labels.json"
xlsx_file = "sources/comparison_report.xlsx"
images_dir = "sources/test-images"
if not os.path.exists(ai_file):
# Fallback to backend/sources
ai_file = "backend/sources/ai_results.json"
manual_file = "backend/sources/manual_labels.json"
xlsx_file = "backend/sources/comparison_report.xlsx"
images_dir = "backend/sources/test-images"
if not os.path.exists(ai_file):
print(f"Error: AI results file not found at {ai_file}")
return
if not os.path.exists(manual_file):
print(f"Error: Manual labels file not found at {manual_file}")
return
# Load data
with open(ai_file, "r", encoding="utf-8") as f:
ai_data = json.load(f)
with open(manual_file, "r", encoding="utf-8") as f:
manual_data = json.load(f)
# Convert to dict for lookup by filename
ai_dict = {item.get("filename"): item for item in ai_data if item.get("filename")}
manual_dict = {item.get("filename"): item for item in manual_data if item.get("filename")}
print(f"Loaded {len(ai_dict)} AI results from file.")
print(f"Loaded {len(manual_dict)} manual labels from file.")
# Scan for physical image files in test-images folder
existing_images = None
if os.path.exists(images_dir):
existing_images = set(os.listdir(images_dir))
print(f"Found {len(existing_images)} physical images in '{images_dir}'.")
else:
print(f"Warning: Images directory not found at '{images_dir}'.")
# Find mismatches in file lists
only_in_ai = set(ai_dict.keys()) - set(manual_dict.keys())
only_in_manual = set(manual_dict.keys()) - set(ai_dict.keys())
if only_in_ai:
print(f"Warning: {len(only_in_ai)} files exist only in AI results: {only_in_ai}")
if only_in_manual:
print(f"Warning: {len(only_in_manual)} files exist only in Manual labels: {only_in_manual}")
# Determine files to compare (must exist in AI results, Manual labels, and physically as images if directory is available)
common_filenames = set(ai_dict.keys()) & set(manual_dict.keys())
if existing_images is not None:
deleted_images = common_filenames - existing_images
if deleted_images:
print(f"Info: Excluded {len(deleted_images)} files that were physically deleted from images folder: {deleted_images}")
all_filenames = sorted(list(common_filenames & existing_images))
else:
all_filenames = sorted(list(common_filenames))
print(f"Comparing {len(all_filenames)} matching images.")
header_fields = [
("noPO", "noPO", "PO Number"),
("noSO", "noSO", "SO Number"),
("noDO", "noDO", "DO Number"),
("tanggal", "tanggal", "Date"),
("plat", "platTruk", "Plat Nomor"),
("customer", "customerInfo", "Customer Name"),
("store", "orderUntuk", "Store Name"),
("alamat", "alamat", "Alamat")
]
doc_comparison_rows = []
item_comparison_rows = []
# Counters for accuracy calculation
stats = {
"PO Number": {"match": 0, "total": 0},
"SO Number": {"match": 0, "total": 0},
"DO Number": {"match": 0, "total": 0},
"Date": {"match": 0, "total": 0},
"Plat Nomor": {"match": 0, "total": 0},
"Customer Name": {"match": 0, "total": 0},
"Store Name": {"match": 0, "total": 0},
"Alamat": {"match": 0, "total": 0},
"Item SKU": {"match": 0, "total": 0},
"Item Banyak": {"match": 0, "total": 0},
"Item Jumlah": {"match": 0, "total": 0}
}
# Document-level side-by-side rows
doc_side_by_side_rows = []
for filename in all_filenames:
manual = manual_dict.get(filename)
ai = ai_dict.get(filename)
if not manual:
print(f"Warning: Manual label not found for {filename} (exists only in AI results)")
continue
if not ai:
print(f"Warning: AI result not found for {filename} (exists only in Manual labels)")
continue
ai_meta = ai.get("layer3Final", {})
# 1. Compare header fields (Vertical format for filtering)
sxs_row = {"Filename": filename}
for manual_key, ai_key, field_label in header_fields:
m_val = clean_val(manual.get(manual_key))
a_val = clean_val(ai_meta.get(ai_key))
is_match = (m_val == a_val)
doc_comparison_rows.append({
"Filename": filename,
"Field": field_label,
"AI Value (OCR)": a_val if a_val else "(empty)",
"Manual Value (Ground Truth)": m_val if m_val else "(empty)",
"Match": "Match" if is_match else "Mismatch"
})
# Side-by-side
sxs_row[f"{field_label} (AI)"] = a_val if a_val else ""
sxs_row[f"{field_label} (Manual)"] = m_val if m_val else ""
sxs_row[f"{field_label} Status"] = "Match" if is_match else "Mismatch"
stats[field_label]["total"] += 1
if is_match:
stats[field_label]["match"] += 1
doc_side_by_side_rows.append(sxs_row)
# 2. Compare items
m_items = manual.get("items", [])
ai_items = ai.get("items", [])
if not ai_items and "items" in ai_meta:
ai_items = ai_meta.get("items", [])
# Create dictionaries of items indexed by codeBarang (SKU)
m_items_dict = {clean_val(item.get("kodeBarang")): item for item in m_items if clean_val(item.get("kodeBarang"))}
ai_items_dict = {clean_val(item.get("kodeBarang")): item for item in ai_items if clean_val(item.get("kodeBarang"))}
# Check all unique SKUs across both manual and AI
all_skus = set(list(m_items_dict.keys()) + list(ai_items_dict.keys()))
for sku in all_skus:
m_item = m_items_dict.get(sku)
ai_item = ai_items_dict.get(sku)
# Check SKU existence match
sku_match = (m_item is not None) and (ai_item is not None)
stats["Item SKU"]["total"] += 1
if sku_match:
stats["Item SKU"]["match"] += 1
m_banyak = clean_val(m_item.get("banyak")) if m_item else ""
ai_banyak = clean_val(ai_item.get("banyak")) if ai_item else ""
banyak_match = (m_banyak == ai_banyak)
stats["Item Banyak"]["total"] += 1
if banyak_match:
stats["Item Banyak"]["match"] += 1
m_jumlah = clean_val(m_item.get("jumlah")) if m_item else ""
ai_jumlah = clean_val(ai_item.get("jumlah")) if ai_item else ""
jumlah_match = (m_jumlah == ai_jumlah)
stats["Item Jumlah"]["total"] += 1
if jumlah_match:
stats["Item Jumlah"]["match"] += 1
# Log code comparison
item_comparison_rows.append({
"Filename": filename,
"Kode Barang (SKU)": sku,
"Field": "SKU Existence",
"AI Value (OCR)": sku if ai_item else "(not found)",
"Manual Value (Ground Truth)": sku if m_item else "(not found)",
"Match": "Match" if sku_match else "Mismatch"
})
# Log Banyak comparison
item_comparison_rows.append({
"Filename": filename,
"Kode Barang (SKU)": sku,
"Field": "Banyak (Qty Package)",
"AI Value (OCR)": ai_banyak if ai_banyak else "(empty)",
"Manual Value (Ground Truth)": m_banyak if m_banyak else "(empty)",
"Match": "Match" if banyak_match else "Mismatch"
})
# Log Jumlah comparison
item_comparison_rows.append({
"Filename": filename,
"Kode Barang (SKU)": sku,
"Field": "Jumlah (Qty Unit)",
"AI Value (OCR)": ai_jumlah if ai_jumlah else "(empty)",
"Manual Value (Ground Truth)": m_jumlah if m_jumlah else "(empty)",
"Match": "Match" if jumlah_match else "Mismatch"
})
# Prepare summary data
summary_rows = []
total_matches = 0
total_fields = 0
for field_label, counts in stats.items():
match_cnt = counts["match"]
total_cnt = counts["total"]
pct = (match_cnt / total_cnt * 100.0) if total_cnt > 0 else 100.0
summary_rows.append({
"Field / Area": field_label,
"Total Checks": total_cnt,
"Matches": match_cnt,
"Mismatches": total_cnt - match_cnt,
"Accuracy (%)": round(pct, 2)
})
total_matches += match_cnt
total_fields += total_cnt
overall_accuracy = (total_matches / total_fields * 100.0) if total_fields > 0 else 100.0
summary_rows.append({
"Field / Area": "OVERALL TOTAL",
"Total Checks": total_fields,
"Matches": total_matches,
"Mismatches": total_fields - total_matches,
"Accuracy (%)": round(overall_accuracy, 2)
})
df_summary = pd.DataFrame(summary_rows)
df_docs = pd.DataFrame(doc_comparison_rows)
df_sxs = pd.DataFrame(doc_side_by_side_rows)
df_items = pd.DataFrame(item_comparison_rows)
# Styling setup
font_family = "Segoe UI"
header_font = Font(name=font_family, size=11, bold=True, color="FFFFFF")
regular_font = Font(name=font_family, size=10)
bold_font = Font(name=font_family, size=10, bold=True)
title_font = Font(name=font_family, size=16, bold=True, color="1F4E78")
header_fill = PatternFill(start_color="1F4E78", end_color="1F4E78", fill_type="solid") # Dark Blue
zebra_fill = PatternFill(start_color="F2F5F8", end_color="F2F5F8", fill_type="solid") # Zebra light blue-gray
match_fill = PatternFill(start_color="E2EFDA", end_color="E2EFDA", fill_type="solid") # Light green
mismatch_fill = PatternFill(start_color="FCE4D6", end_color="FCE4D6", fill_type="solid") # Light orange
center_align = Alignment(horizontal="center", vertical="center")
left_align = Alignment(horizontal="left", vertical="center")
right_align = Alignment(horizontal="right", vertical="center")
thin_side = Side(border_style="thin", color="D9D9D9")
cell_border = Border(left=thin_side, right=thin_side, top=thin_side, bottom=thin_side)
# Save to Excel
os.makedirs(os.path.dirname(xlsx_file), exist_ok=True)
writer = None
for attempt in range(1, 10):
try:
writer = pd.ExcelWriter(xlsx_file, engine='openpyxl')
break
except PermissionError:
base_dir = os.path.dirname(xlsx_file)
filename = os.path.basename(xlsx_file)
name, ext = os.path.splitext(filename)
if "_" in name and name.split("_")[-1].isdigit():
name = "_".join(name.split("_")[:-1])
xlsx_file = os.path.join(base_dir, f"{name}_{attempt}{ext}")
if writer is None:
print("Error: Could not open the Excel writer because the file is locked.")
return
with writer:
df_summary.to_excel(writer, sheet_name='Summary Accuracy', index=False, startrow=3)
df_sxs.to_excel(writer, sheet_name='Side-by-Side Comparison', index=False)
df_docs.to_excel(writer, sheet_name='Header Field Comparison', index=False)
df_items.to_excel(writer, sheet_name='Item SKU Comparison', index=False)
# 1. Style Summary Sheet with a Title Banner
ws_sum = writer.sheets['Summary Accuracy']
ws_sum.views.sheetView[0].showGridLines = True
ws_sum.cell(row=1, column=1, value="AI OCR vs. Manual Ground Truth Accuracy Report").font = title_font
ws_sum.row_dimensions[1].height = 30
# Style Summary Table Headers
max_col_sum = df_summary.shape[1]
for col in range(1, max_col_sum + 1):
cell = ws_sum.cell(row=4, column=col)
cell.font = header_font
cell.fill = header_fill
cell.alignment = center_align
cell.border = cell_border
# Style Summary Data
max_row_sum = ws_sum.max_row
for row in range(5, max_row_sum + 1):
for col in range(1, max_col_sum + 1):
cell = ws_sum.cell(row=row, column=col)
cell.font = regular_font
cell.border = cell_border
if col == 1:
cell.alignment = left_align
else:
cell.alignment = right_align
# Zebra style
if row % 2 == 0 and row != max_row_sum:
cell.fill = zebra_fill
# Format percentage
if col == 5 and isinstance(cell.value, (int, float)):
cell.number_format = '0.00"%"'
# Bold overall total row
if row == max_row_sum:
for col in range(1, max_col_sum + 1):
c = ws_sum.cell(row=row, column=col)
c.font = bold_font
c.fill = match_fill if overall_accuracy > 80 else mismatch_fill
# Auto-adjust column width for Summary
for col in ws_sum.columns:
max_len = max(len(str(cell.value or '')) for cell in col)
col_letter = get_column_letter(col[0].column)
ws_sum.column_dimensions[col_letter].width = max(max_len + 4, 12)
# Style detail worksheets
for sheet_name in ['Side-by-Side Comparison', 'Header Field Comparison', 'Item SKU Comparison']:
ws = writer.sheets[sheet_name]
ws.views.sheetView[0].showGridLines = True
max_row = ws.max_row
max_col = ws.max_column
# Header row styling
for col in range(1, max_col + 1):
cell = ws.cell(row=1, column=col)
cell.font = header_font
cell.fill = header_fill
cell.alignment = center_align
cell.border = cell_border
# Data rows styling
for row in range(2, max_row + 1):
is_zebra = (row % 2 == 0)
# For Side-by-Side Comparison
if sheet_name == 'Side-by-Side Comparison':
for col in range(1, max_col + 1):
cell = ws.cell(row=row, column=col)
cell.font = regular_font
cell.border = cell_border
if col == 1:
cell.alignment = left_align
if is_zebra:
cell.fill = zebra_fill
else:
# Apply alignments and color mismatch/match
# Format of headers:
# Col 1: Filename
# Col 2: PO AI, Col 3: PO Manual, Col 4: PO Status
# ... and so on
# So status is at index col where (col - 1) % 3 == 0 (4, 7, 10, 13, 16, 19, 22, 25)
col_pos = col - 1
if col_pos % 3 == 0: # This is a Status column
status_val = cell.value
cell.alignment = center_align
if status_val == "Match":
cell.fill = match_fill
else:
cell.fill = mismatch_fill
else: # This is AI or Manual value column
cell.alignment = left_align
# Match background of the cell with its corresponding status cell (two columns to the right if AI, one if Manual)
status_col_idx = col + (2 if col_pos % 3 == 1 else 1)
status_val = ws.cell(row=row, column=status_col_idx).value
if status_val == "Match":
if is_zebra:
# Let's keep it subtle
pass
else:
# Highlight mismatches clearly
cell.fill = mismatch_fill
# For vertical comparison sheets
else:
# Match column is the last column
match_cell = ws.cell(row=row, column=max_col)
match_val = match_cell.value
for col in range(1, max_col + 1):
cell = ws.cell(row=row, column=col)
cell.font = regular_font
cell.border = cell_border
# Apply alignments based on column
if col in [1, 2, 3, 4]:
cell.alignment = left_align
else:
cell.alignment = center_align
# Color match / mismatch
if match_val == "Match":
cell.fill = match_fill
elif match_val == "Mismatch":
cell.fill = mismatch_fill
elif is_zebra:
cell.fill = zebra_fill
# Auto-fit columns
for col in ws.columns:
max_len = 0
for cell in col:
val_str = str(cell.value or '')
# Limit long text like Alamat from making column excessively wide
if sheet_name == 'Side-by-Side Comparison' and cell.column in [22, 23]: # Alamat
max_len = max(max_len, min(len(val_str), 30))
elif sheet_name == 'Header Field Comparison' and cell.column in [3, 4]: # Values
max_len = max(max_len, min(len(val_str), 40))
else:
max_len = max(max_len, len(val_str))
col_letter = get_column_letter(col[0].column)
ws.column_dimensions[col_letter].width = max(max_len + 4, 12)
print("\n=== Accuracy Report Summary ===")
print(df_summary.to_string(index=False))
print("===============================\n")
print(f"Comparison report generated at {xlsx_file}")
if __name__ == "__main__":
main()
+1
View File
@@ -7,6 +7,7 @@ const nextConfig: NextConfig = {
"*.trycloudflare.com", "*.trycloudflare.com",
"*.ngrok.io", "*.ngrok.io",
"*.ngrok-free.app", "*.ngrok-free.app",
"*.ngrok-free.dev",
"*.ngrok.app", "*.ngrok.app",
"*.loca.lt", "*.loca.lt",
"*.serveo.net", "*.serveo.net",
Binary file not shown.
+15 -17
View File
@@ -7,35 +7,19 @@ const BASE_URL = 'http://localhost:3000/api/parse';
const testFiles = [ const testFiles = [
"1782870899198-sample_do.jpeg", "1782870899198-sample_do.jpeg",
"1782872141829-rotated_1782872118246_rotated_1782872112296_rotated_1782872107513_CAP3481952560331460805.jpg",
"1782875271815-rotated_1782875258464_rotated_1782875254091_rotated_1782875250123_CAP1599226410575173446.jpg",
"1782884664859-rotated_1782884661516_rotated_1782884658097_rotated_1782884654697_CAP5956452738616729026.jpg", "1782884664859-rotated_1782884661516_rotated_1782884658097_rotated_1782884654697_CAP5956452738616729026.jpg",
"1782888127716-CAP6161747431193129837.jpg", "1782888127716-CAP6161747431193129837.jpg",
"1782888211457-rotated_1782888204591_rotated_1782888199962_rotated_1782888195402_CAP7202641176939787142.jpg", "1782888211457-rotated_1782888204591_rotated_1782888199962_rotated_1782888195402_CAP7202641176939787142.jpg",
"1782888609885-rotated_1782888593813_1000000454.jpg", "1782888609885-rotated_1782888593813_1000000454.jpg",
"1782890303600-1000000465.jpg", "1782890303600-1000000465.jpg",
"1782892728794-1000000466.jpg", "1782892728794-1000000466.jpg",
"1782909791970-sample_do.jpeg",
"IMG_20260630_145445.jpg", "IMG_20260630_145445.jpg",
"IMG_20260701_134446.jpg", "IMG_20260701_134446.jpg",
"IMG_20260701_134504.jpg", "IMG_20260701_134504.jpg",
"IMG_20260701_134533.jpg",
"IMG_20260701_134555.jpg", "IMG_20260701_134555.jpg",
"IMG_20260701_134555~2.jpg", "IMG_20260701_134555~2.jpg",
"IMG_20260701_134635.jpg",
"IMG_20260701_134636.jpg",
"IMG_20260701_134646.jpg",
"IMG_20260701_134646~2.jpg", "IMG_20260701_134646~2.jpg",
"IMG_20260701_134704.jpg", "IMG_20260701_134810.jpg"
"IMG_20260701_134704~2.jpg",
"IMG_20260701_134810.jpg",
"IMG_20260701_134817.jpg",
"IMG_20260701_135900.jpg",
"IMG_20260701_135910.jpg",
"IMG_20260701_135923.jpg",
"IMG_20260701_135938.jpg",
"IMG_20260701_135950.jpg",
"IMG_20260701_140005.jpg"
]; ];
function postJSON(url, body) { function postJSON(url, body) {
@@ -203,6 +187,10 @@ async function main() {
status: 'Success', status: 'Success',
tilt: tiltStr, tilt: tiltStr,
unwarped: unwarpedStr, unwarped: unwarpedStr,
rawMarkdown: resData.postProcessingDetails?.rawMarkdown || "",
layer1RawRegex: resData.postProcessingDetails?.layer1RawRegex || {},
layer2Sanitized: resData.postProcessingDetails?.layer2Sanitized || {},
layer3Final: resData.postProcessingDetails?.layer3Final || {},
metadata: docMeta, metadata: docMeta,
items: resData.items || [] items: resData.items || []
}) + '\n'); }) + '\n');
@@ -240,6 +228,16 @@ async function main() {
console.error('Failed to combine test reports:', combineErr); console.error('Failed to combine test reports:', combineErr);
} }
// Compile JSONL into the final JSON v2
try {
const lines = fs.readFileSync(jsonlFile, 'utf8').split('\n').filter(Boolean);
const results = lines.map(line => JSON.parse(line));
fs.writeFileSync('/uploads/ai_results_v2.json', JSON.stringify(results, null, 2));
console.log('Compiled results saved to /uploads/ai_results_v2.json');
} catch (compileErr) {
console.error('Failed to compile results into JSON v2:', compileErr);
}
console.log('Batch test completed. Report written to /uploads/test_images_report.md'); console.log('Batch test completed. Report written to /uploads/test_images_report.md');
} }
+403
View File
@@ -0,0 +1,403 @@
/**
* run_full_test.js
*
* Runs OCR parsing against ALL images in backend/sources/test-images/
* and captures every pipeline stage for analysis:
* - rawMarkdown : raw text from PaddleOCR layout parser
* - layer1RawRegex: output of parseDOMetadata (regex extraction)
* - layer2Sanitized: output of sanitizeParsedMetadata (format checks)
* - layer3Final : final metadata after SKU triple-check + store resolution
*
* Outputs:
* backend/sources/ai_results.json — machine-readable per-file results
* backend/sources/ai_results.md — human-readable stage-by-stage breakdown
*
* Usage (from host machine, Docker must be running):
* node run_full_test.js
*
* The script talks to the nginx gateway on port 8000.
* To override: set env var BASE_URL=http://localhost:3000/api/parse
*/
const fs = require('fs');
const path = require('path');
const http = require('http');
const https = require('https');
// ─── Config ──────────────────────────────────────────────────────────────────
const BASE_URL = process.env.BASE_URL || 'http://localhost:8000/api/parse';
const TEST_IMAGES_DIR = path.resolve(__dirname, '../sources/test-images');
const OUTPUT_JSON = path.resolve(__dirname, '../sources/ai_results.json');
const OUTPUT_MD = path.resolve(__dirname, '../sources/ai_results.md');
const REQUEST_TIMEOUT_MS = 20 * 60 * 1000; // 20 minutes per image
// ─── HTTP Helper ─────────────────────────────────────────────────────────────
function postJSON(url, body) {
return new Promise((resolve, reject) => {
const parsedUrl = new URL(url);
const bodyStr = JSON.stringify(body);
const lib = parsedUrl.protocol === 'https:' ? https : http;
const options = {
hostname: parsedUrl.hostname,
port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
path: parsedUrl.pathname + parsedUrl.search,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(bodyStr),
},
timeout: REQUEST_TIMEOUT_MS,
};
const req = lib.request(options, (res) => {
let data = '';
res.on('data', (chunk) => { data += chunk; });
res.on('end', () => {
resolve({
ok: res.statusCode >= 200 && res.statusCode < 300,
status: res.statusCode,
body: data,
});
});
});
req.on('timeout', () => {
req.destroy(new Error(`Request timed out after ${REQUEST_TIMEOUT_MS / 60000}m`));
});
req.on('error', reject);
req.write(bodyStr);
req.end();
});
}
// ─── Markdown Helpers ─────────────────────────────────────────────────────────
function mdSection(title, level = 2) {
return `${'#'.repeat(level)} ${title}\n\n`;
}
function mdCode(content, lang = '') {
if (content === null || content === undefined) return '*null*\n\n';
const str = typeof content === 'string' ? content : JSON.stringify(content, null, 2);
return `\`\`\`${lang}\n${str}\n\`\`\`\n\n`;
}
function mdField(label, value) {
const display = (value === null || value === undefined || value === '') ? '*empty*' : `\`${value}\``;
return `- **${label}**: ${display}\n`;
}
function mdTable(headers, rows) {
if (!rows || rows.length === 0) return '*No items.*\n\n';
const sep = headers.map(() => '---');
const lines = [
`| ${headers.join(' | ')} |`,
`| ${sep.join(' | ')} |`,
...rows.map(r => `| ${r.map(c => String(c ?? '').replace(/\|/g, '\\|')).join(' | ')} |`),
];
return lines.join('\n') + '\n\n';
}
// ─── Main ─────────────────────────────────────────────────────────────────────
async function main() {
// Discover all image files
let files;
try {
files = fs.readdirSync(TEST_IMAGES_DIR).filter(f =>
/\.(jpe?g|png|webp|bmp)$/i.test(f)
).sort();
} catch (e) {
console.error(`Cannot read test-images directory: ${TEST_IMAGES_DIR}`);
console.error(e.message);
process.exit(1);
}
if (files.length === 0) {
console.error('No image files found in', TEST_IMAGES_DIR);
process.exit(1);
}
console.log(`\n🚀 Starting batch test`);
console.log(` API endpoint : ${BASE_URL}`);
console.log(` Images found : ${files.length}`);
console.log(` Output JSON : ${OUTPUT_JSON}`);
console.log(` Output MD : ${OUTPUT_MD}`);
console.log('─'.repeat(60));
const jsonResults = [];
const mdParts = [];
const summaryRows = [];
// ── Markdown document header ──────────────────────────────────────────────
mdParts.push(
`# OCR Batch Test Report\n\n`,
`> Generated: ${new Date().toISOString()}\n`,
`> API: \`${BASE_URL}\`\n`,
`> Images: **${files.length}** files from \`backend/sources/test-images/\`\n\n`,
`---\n\n`,
`## Summary\n\n`,
'<!-- summary_table_placeholder -->\n\n',
`---\n\n`,
`## Stage-by-Stage Results\n\n`,
);
const summaryPlaceholderIndex = mdParts.indexOf('<!-- summary_table_placeholder -->\n\n');
// ── Process each file ────────────────────────────────────────────────────
for (let idx = 0; idx < files.length; idx++) {
const file = files[idx];
const num = `[${String(idx + 1).padStart(2, '0')}/${files.length}]`;
process.stdout.write(`${num} ${file} ... `);
const entry = {
index: idx + 1,
filename: file,
status: 'pending',
tilt: null,
unwarped: null,
// pipeline stages
rawMarkdown: null,
layer1RawRegex: null,
layer2Sanitized: null,
layer3Final: null,
items: [],
error: null,
};
try {
const t0 = Date.now();
const res = await postJSON(BASE_URL, { filename: file });
const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
if (!res.ok) {
process.stdout.write(`❌ HTTP ${res.status} (${elapsed}s)\n`);
entry.status = 'http_error';
entry.error = `HTTP ${res.status}: ${res.body}`;
} else {
let data;
try {
data = JSON.parse(res.body);
} catch (_) {
entry.status = 'json_parse_error';
entry.error = 'Response is not valid JSON';
process.stdout.write(`❌ JSON parse error (${elapsed}s)\n`);
data = null;
}
if (data) {
if (data.error) {
process.stdout.write(`⚠️ API error: ${data.error} (${elapsed}s)\n`);
entry.status = 'api_error';
entry.error = data.error;
} else {
const pipelineInfo = (data.result || {}).pipeline_info || {};
entry.status = 'success';
entry.tilt = pipelineInfo.tilt !== undefined ? +parseFloat(pipelineInfo.tilt).toFixed(2) : null;
entry.unwarped = pipelineInfo.unwarped ?? null;
const ppd = data.postProcessingDetails || {};
entry.rawMarkdown = ppd.rawMarkdown ?? null;
entry.layer1RawRegex = ppd.layer1RawRegex ?? null;
entry.layer2Sanitized = ppd.layer2Sanitized ?? null;
entry.layer3Final = ppd.layer3Final ?? null;
entry.items = data.items ?? [];
const itemCount = entry.items.length;
process.stdout.write(`✅ ${itemCount} item(s), tilt=${entry.tilt ?? 'N/A'}° (${elapsed}s)\n`);
}
}
}
} catch (err) {
process.stdout.write(`💥 ${err.message}\n`);
entry.status = 'exception';
entry.error = err.message;
}
jsonResults.push(entry);
// ── Build per-file markdown section ─────────────────────────────────────
const statusEmoji = {
success: '✅',
http_error: '❌',
api_error: '⚠️',
json_parse_error: '❌',
exception: '💥',
}[entry.status] || '❓';
let fileMd = '';
fileMd += `### ${idx + 1}. \`${file}\`\n\n`;
fileMd += `**Status**: ${statusEmoji} \`${entry.status}\`\n\n`;
if (entry.status !== 'success') {
fileMd += `> **Error**: ${entry.error}\n\n`;
fileMd += `---\n\n`;
summaryRows.push([idx + 1, `\`${file}\``, `${statusEmoji} ${entry.status}`, 'N/A', 'N/A', 'N/A', 'N/A']);
mdParts.push(fileMd);
continue;
}
// ── Stage 0: Pipeline Info ────────────────────────────────────────────────
fileMd += `#### 📐 Stage 0 — Pipeline Info\n\n`;
fileMd += mdField('Tilt detected', entry.tilt !== null ? `${entry.tilt}°` : 'N/A');
fileMd += mdField('Auto-unwarped', entry.unwarped !== null ? (entry.unwarped ? 'Yes' : 'No') : 'N/A');
fileMd += '\n';
// ── Stage 1: Raw Markdown from OCR ───────────────────────────────────────
fileMd += `#### 📄 Stage 1 — Raw OCR Markdown\n\n`;
fileMd += `*This is the raw text extracted by PaddleOCR layout parser before any post-processing.*\n\n`;
if (entry.rawMarkdown) {
fileMd += mdCode(entry.rawMarkdown, 'markdown');
} else {
fileMd += '*No raw markdown captured.*\n\n';
}
// ── Stage 2: Layer 1 — Regex Extraction ──────────────────────────────────
fileMd += `#### 🔍 Stage 2 — Layer 1: Regex Extraction (\`parseDOMetadata\`)\n\n`;
fileMd += `*Regex patterns are applied to raw markdown to extract header fields and item rows.*\n\n`;
if (entry.layer1RawRegex) {
const l1 = entry.layer1RawRegex;
fileMd += `**Header fields (raw regex output):**\n\n`;
fileMd += mdField('noDO', l1.noDO);
fileMd += mdField('noPO', l1.noPO);
fileMd += mdField('noSO', l1.noSO);
fileMd += mdField('tanggal', l1.tanggal);
fileMd += mdField('vendorInfo', l1.vendorInfo);
fileMd += mdField('customerInfo', l1.customerInfo);
fileMd += mdField('alamat', l1.alamat);
fileMd += mdField('orderUntuk', l1.orderUntuk);
fileMd += mdField('platTruk', l1.platTruk);
fileMd += '\n';
fileMd += `**Raw items (${(l1.items || []).length} row(s)):**\n\n`;
fileMd += mdTable(
['kodeBarang', 'namaBarang', 'banyak', 'jumlah'],
(l1.items || []).map(it => [it.kodeBarang, it.namaBarang, it.banyak, it.jumlah])
);
} else {
fileMd += '*Layer 1 data not captured.*\n\n';
}
// ── Stage 3: Layer 2 — Sanitized ─────────────────────────────────────────
fileMd += `#### 🧹 Stage 3 — Layer 2: Sanitized (\`sanitizeParsedMetadata\`)\n\n`;
fileMd += `*Strict format enforcement: corrects date formats, trims whitespace, enforces field constraints.*\n\n`;
if (entry.layer2Sanitized) {
const l2 = entry.layer2Sanitized;
fileMd += `**Header fields (after sanitization):**\n\n`;
fileMd += mdField('noDO', l2.noDO);
fileMd += mdField('noPO', l2.noPO);
fileMd += mdField('noSO', l2.noSO);
fileMd += mdField('tanggal', l2.tanggal);
fileMd += mdField('vendorInfo', l2.vendorInfo);
fileMd += mdField('customerInfo', l2.customerInfo);
fileMd += mdField('alamat', l2.alamat);
fileMd += mdField('orderUntuk', l2.orderUntuk);
fileMd += mdField('platTruk', l2.platTruk);
fileMd += '\n';
fileMd += `**Sanitized items (${(l2.items || []).length} row(s)):**\n\n`;
fileMd += mdTable(
['kodeBarang', 'namaBarang', 'banyak', 'jumlah'],
(l2.items || []).map(it => [it.kodeBarang, it.namaBarang, it.banyak, it.jumlah])
);
} else {
fileMd += '*Layer 2 data not captured.*\n\n';
}
// ── Stage 4: Layer 3 — Final (SKU triple-check + store resolution) ────────
fileMd += `#### ✅ Stage 4 — Layer 3: Final (\`SKU triple-check + store resolution\`)\n\n`;
fileMd += `*SKU validated against master list (score ≥ 0.6 threshold). Items with noise SKU codes are filtered out. Store resolved from DB.*\n\n`;
if (entry.layer3Final) {
const l3 = entry.layer3Final;
fileMd += `**Final metadata:**\n\n`;
fileMd += mdField('noDO', l3.noDO);
fileMd += mdField('noPO', l3.noPO);
fileMd += mdField('noSO', l3.noSO);
fileMd += mdField('tanggal', l3.tanggal);
fileMd += mdField('vendorInfo', l3.vendorInfo);
fileMd += mdField('customerInfo', l3.customerInfo);
fileMd += mdField('alamat', l3.alamat);
fileMd += mdField('orderUntuk', l3.orderUntuk);
fileMd += mdField('platTruk', l3.platTruk);
fileMd += '\n';
fileMd += `**Final items after SKU validation (${(l3.items || []).length} row(s)):**\n\n`;
fileMd += mdTable(
['kodeBarangOriginal', 'kodeBarang (corrected)', 'namaBarang', 'banyak', 'jumlah'],
(l3.items || []).map(it => [
it.kodeBarangOriginal ?? it.kodeBarang,
it.kodeBarang,
it.namaBarang,
it.banyak,
it.jumlah
])
);
} else {
fileMd += '*Layer 3 data not captured.*\n\n';
}
// ── Stage 5: Final submitted items (from root items[]) ───────────────────
fileMd += `#### 🗃️ Stage 5 — Submitted Items (ready-to-use JSON)\n\n`;
fileMd += `*These are the items actually returned to the caller and saved to the database.*\n\n`;
fileMd += mdTable(
['kodeBarangOriginal', 'kodeBarang', 'namaBarang', 'banyak', 'jumlah'],
(entry.items || []).map(it => [
it.kodeBarangOriginal ?? it.kodeBarang,
it.kodeBarang,
it.namaBarang,
it.banyak,
it.jumlah
])
);
fileMd += `---\n\n`;
// ── Summary row ──────────────────────────────────────────────────────────
const l3meta = entry.layer3Final || {};
summaryRows.push([
idx + 1,
`\`${file}\``,
`${statusEmoji} success`,
entry.tilt !== null ? `${entry.tilt}°` : 'N/A',
entry.unwarped !== null ? (entry.unwarped ? 'Yes' : 'No') : 'N/A',
`\`${l3meta.noDO ?? 'N/A'}\``,
`\`${l3meta.noPO ?? 'N/A'}\``,
`${entry.items.length}`,
]);
mdParts.push(fileMd);
}
// ── Inject summary table ──────────────────────────────────────────────────
const summaryTable = mdTable(
['#', 'Filename', 'Status', 'Tilt', 'Unwarped', 'DO', 'PO', 'Items'],
summaryRows
);
mdParts[summaryPlaceholderIndex] = summaryTable;
// ── Write outputs ─────────────────────────────────────────────────────────
const jsonOut = JSON.stringify(jsonResults, null, 2);
fs.writeFileSync(OUTPUT_JSON, jsonOut, 'utf8');
console.log(`\n✅ JSON saved → ${OUTPUT_JSON}`);
const mdOut = mdParts.join('');
fs.writeFileSync(OUTPUT_MD, mdOut, 'utf8');
console.log(`✅ MD saved → ${OUTPUT_MD}`);
// ── Final stats ───────────────────────────────────────────────────────────
const succeeded = jsonResults.filter(r => r.status === 'success').length;
const failed = jsonResults.length - succeeded;
console.log('\n─'.repeat(60));
console.log(` Total : ${jsonResults.length}`);
console.log(` Success: ${succeeded}`);
console.log(` Failed : ${failed}`);
console.log('─'.repeat(60));
}
main().catch(err => {
console.error('Fatal error:', err);
process.exit(1);
});
+35 -3
View File
@@ -50,6 +50,8 @@ export async function POST(req: NextRequest) {
let isUpload = false; let isUpload = false;
let isSample = true; let isSample = true;
if (!fs.existsSync(filePath)) {
filePath = path.join(process.cwd(), "public", "test-images", safeFile);
if (!fs.existsSync(filePath)) { if (!fs.existsSync(filePath)) {
filePath = path.join("/uploads", safeFile); filePath = path.join("/uploads", safeFile);
if (!fs.existsSync(filePath)) { if (!fs.existsSync(filePath)) {
@@ -58,6 +60,7 @@ export async function POST(req: NextRequest) {
isUpload = true; isUpload = true;
isSample = false; isSample = false;
} }
}
// Read file and compute content hash // Read file and compute content hash
const fileBuffer = fs.readFileSync(filePath); const fileBuffer = fs.readFileSync(filePath);
@@ -156,6 +159,7 @@ export async function POST(req: NextRequest) {
const rawMetadata = parseDOMetadata(markdownText); const rawMetadata = parseDOMetadata(markdownText);
// Second-layer sanity check: enforces strict field formats and auto-corrects anomalies // Second-layer sanity check: enforces strict field formats and auto-corrects anomalies
const docMetadata = sanitizeParsedMetadata(rawMetadata as any); const docMetadata = sanitizeParsedMetadata(rawMetadata as any);
const docMetadataSanitized = JSON.parse(JSON.stringify(docMetadata));
// Load SKU Master list from DB (including new packaging details for triple check validation) // Load SKU Master list from DB (including new packaging details for triple check validation)
const skuDbRes = await query("SELECT no_sku, nama_item, standar_jumlah, jenis_outer FROM sku_master"); const skuDbRes = await query("SELECT no_sku, nama_item, standar_jumlah, jenis_outer FROM sku_master");
@@ -256,7 +260,14 @@ export async function POST(req: NextRequest) {
const numericQtyMatch = ocrQty.match(/[\d.,]+/); const numericQtyMatch = ocrQty.match(/[\d.,]+/);
if (numericQtyMatch) { if (numericQtyMatch) {
const numericQty = numericQtyMatch[0]; const numericQty = numericQtyMatch[0];
const qtyUnitMatch = ocrQty.match(/[a-zA-Z]+/);
const parsedQtyUnit = qtyUnitMatch ? qtyUnitMatch[0].toUpperCase() : "";
const isQtyUnitValid = ["KRG", "BOX", "BAG", "PAC", "PC", "KG", "PCS"].includes(parsedQtyUnit);
const standardOuter = bestMatch.jenis_outer || ""; const standardOuter = bestMatch.jenis_outer || "";
if (isQtyUnitValid) {
item.banyak = `${numericQty} ${parsedQtyUnit}`;
} else if (standardOuter) {
if (standardOuter.toLowerCase() === "karung") { if (standardOuter.toLowerCase() === "karung") {
item.banyak = `${numericQty} KRG`; item.banyak = `${numericQty} KRG`;
} else if (standardOuter.toLowerCase() === "box") { } else if (standardOuter.toLowerCase() === "box") {
@@ -266,14 +277,29 @@ export async function POST(req: NextRequest) {
} else { } else {
item.banyak = `${numericQty} ${standardOuter.toUpperCase()}`; item.banyak = `${numericQty} ${standardOuter.toUpperCase()}`;
} }
} else {
item.banyak = ocrQty;
}
} }
// Auto-standardize Price/Total (jumlah) units // Auto-standardize Price/Total (jumlah) units
const numericPriceMatch = ocrPrice.match(/[\d.,]+/); const numericPriceMatch = ocrPrice.match(/[\d.,]+/);
if (numericPriceMatch) { if (numericPriceMatch) {
const numericPrice = numericPriceMatch[0]; const numericPrice = numericPriceMatch[0];
const standardInner = (bestMatch.standar_jumlah || "").toUpperCase(); const priceUnitMatch = ocrPrice.match(/[a-zA-Z]+/);
item.jumlah = `${numericPrice} ${standardInner}`; const parsedPriceUnit = priceUnitMatch ? priceUnitMatch[0].toUpperCase() : "";
const isPriceUnitValid = ["KRG", "BOX", "BAG", "PAC", "PC", "KG", "PCS"].includes(parsedPriceUnit);
const standardInner = bestMatch.standar_jumlah || "";
if (standardInner.toLowerCase() === "pc" || standardInner.toLowerCase() === "pcs") {
item.jumlah = `${numericPrice} ${standardInner.toUpperCase()}`;
} else if (isPriceUnitValid) {
item.jumlah = `${numericPrice} ${parsedPriceUnit}`;
} else if (standardInner) {
item.jumlah = `${numericPrice} ${standardInner.toUpperCase()}`;
} else {
item.jumlah = ocrPrice;
}
} }
checkedItems.push(item); checkedItems.push(item);
@@ -371,12 +397,18 @@ export async function POST(req: NextRequest) {
]); ]);
} }
// Return wrapped success response with items, flagged, remarks // Return wrapped success response with items, flagged, remarks, and intermediate post-processing details
return NextResponse.json({ return NextResponse.json({
errorCode: 0, errorCode: 0,
errorMsg: "Success", errorMsg: "Success",
result: pipelineResult, result: pipelineResult,
items: docMetadata.items, items: docMetadata.items,
postProcessingDetails: {
rawMarkdown: markdownText,
layer1RawRegex: rawMetadata,
layer2Sanitized: docMetadataSanitized,
layer3Final: docMetadata
},
flagged: {}, flagged: {},
remarks: {} remarks: {}
}); });
@@ -29,20 +29,44 @@ function runTests() {
{ name: "Date (Asli/Copy) prefix noise", markdown: "Tanggal: (Asli/Copy) 15 May 2026\nNo. PO : PO/26/0000178435", expected: { tanggal: "15 May 2026" } }, { name: "Date (Asli/Copy) prefix noise", markdown: "Tanggal: (Asli/Copy) 15 May 2026\nNo. PO : PO/26/0000178435", expected: { tanggal: "15 May 2026" } },
{ name: "Date junk suffix cut", markdown: "Tanggal: 25 May 2020 (Printed by system)", expected: { tanggal: "25 May 2020" } }, { name: "Date junk suffix cut", markdown: "Tanggal: 25 May 2020 (Printed by system)", expected: { tanggal: "25 May 2020" } },
{ name: "Date bad OCR month Hv -> Not Found", markdown: "Tanggal:25 Hv 2024\nNo.SO : 1601001206", expected: { tanggal: "Not Found" } }, { name: "Date bad OCR month Hv -> Not Found", markdown: "Tanggal:25 Hv 2024\nNo.SO : 1601001206", expected: { tanggal: "Not Found" } },
{ name: "Date single digit 7 May 2026 -> 07 May 2026", markdown: "Tanggal: 7 May 2026\nNo. PO : PO/26/0000178435", expected: { tanggal: "07 May 2026" } },
{ name: "Date single digit 4 Apr 2026 -> 04 April 2026", markdown: "Tanggal: 4 Apr 2026\nNo. PO : PO/26/0000178435", expected: { tanggal: "04 April 2026" } },
{ name: "00117709 before Tanggal must not pollute date", markdown: "00117709\nTanggal:25May2020\nNo.SO : 1091721200\nNo. DO : 1657943004\nNo. PO : F0/26/0000190929", expected: { tanggal: "25 May 2020" } }, { name: "00117709 before Tanggal must not pollute date", markdown: "00117709\nTanggal:25May2020\nNo.SO : 1091721200\nNo. DO : 1657943004\nNo. PO : F0/26/0000190929", expected: { tanggal: "25 May 2020" } },
{ name: "Plate B 9427 UXT", markdown: "Truck No. B 9427 UXT\nNo. PO : PO/26/0000178435", expected: { platTruk: "B 9427 UXT" } }, { name: "Plate B 9427 UXT", markdown: "Truck No. B 9427 UXT\nNo. PO : PO/26/0000178435", expected: { platTruk: "B 9427 UXT" } },
{ name: "Plate B-9999-XYZ dash", markdown: "No. Polisi: B-9999-XYZ\nNo. PO : PO/26/0000178435", expected: { platTruk: "B 9999 XYZ" } }, { name: "Plate B-9999-XYZ dash", markdown: "No. Polisi: B-9999-XYZ\nNo. PO : PO/26/0000178435", expected: { platTruk: "B 9999 XYZ" } },
{ name: "Plate ignore PO/SO prefix", markdown: "Plate is PO 1234 SO but real truck is A 123 B\nNo. PO : PO/26/0000178435", expected: { platTruk: "A 123 B" } }, { name: "Plate ignore PO/SO prefix", markdown: "Plate is PO 1234 SO but real truck is A 123 B\nNo. PO : PO/26/0000178435", expected: { platTruk: "A 123 B" } },
{ name: "Plate B9427UXT adjacent", markdown: "No Polisi B9427UXT\nNo. PO : PO/26/0000178435", expected: { platTruk: "B 9427 UXT" } }, { name: "Plate B9427UXT adjacent", markdown: "No Polisi B9427UXT\nNo. PO : PO/26/0000178435", expected: { platTruk: "B 9427 UXT" } },
{ name: "Plate real doc B 9723 CXS", markdown: "Truck No.\nB 9723 CXS\nWH 01 / 01\nNo. PO : PO/26/0000178435", expected: { platTruk: "B 9723 CXS" } }, { name: "Plate real doc B 9723 CXS", markdown: "Truck No.\nB 9723 CXS\nWH 01 / 01\nNo. PO : PO/26/0000178435", expected: { platTruk: "B 9723 CXS" } },
{
name: "Table column shift alignment correction",
markdown: "No. PO : PO/26/0000178435\n" +
"<table>" +
"<tr><td>Kode Barang</td><td>Nama Barang</td><td>Banyak</td><td>Jumlah</td></tr>" +
"<tr><td></td><td>Item A</td><td>2 KRG</td><td>40 PC</td></tr>" +
"<tr><td>11310014</td><td>Item B</td><td>2 KRG</td><td>40 PC</td></tr>" +
"<tr><td>11310024</td><td>Item C</td><td>1 BOX</td><td>10 KG</td></tr>" +
"<tr><td>11720055</td><td></td><td></td><td></td></tr>" +
"</table>",
expected: {
items: [
{ kodeBarang: "11310014", namaBarang: "Item A", banyak: "2 KRG", jumlah: "40 PC" },
{ kodeBarang: "11310024", namaBarang: "Item B", banyak: "2 KRG", jumlah: "40 PC" },
{ kodeBarang: "11720055", namaBarang: "Item C", banyak: "1 BOX", jumlah: "10 KG" }
]
} as any
}
]; ];
for (const t of parseTests) { for (const t of parseTests) {
try { try {
const result = parseDOMetadata(t.markdown) as any; const result = parseDOMetadata(t.markdown) as any;
for (const [key, val] of Object.entries(t.expected)) { for (const [key, val] of Object.entries(t.expected)) {
if (key === "items") {
assert.deepStrictEqual(result.items, val);
} else {
assert.strictEqual(result[key], val, `field [${key}] expected "${val}" got "${result[key]}"`); assert.strictEqual(result[key], val, `field [${key}] expected "${val}" got "${result[key]}"`);
} }
}
console.log(`[PASS] ${t.name}`); console.log(`[PASS] ${t.name}`);
} catch (err: any) { } catch (err: any) {
console.error(`[FAIL] ${t.name}: ${err.message}`); console.error(`[FAIL] ${t.name}: ${err.message}`);
@@ -56,6 +80,7 @@ function runTests() {
// tanggal valid // tanggal valid
{ name: "sanitize: valid tanggal 30 June 2026 passes", input: { tanggal: "30 June 2026" }, expected: { tanggal: "30 June 2026" } }, { name: "sanitize: valid tanggal 30 June 2026 passes", input: { tanggal: "30 June 2026" }, expected: { tanggal: "30 June 2026" } },
{ name: "sanitize: valid tanggal 25 May 2020 passes", input: { tanggal: "25 May 2020" }, expected: { tanggal: "25 May 2020" } }, { name: "sanitize: valid tanggal 25 May 2020 passes", input: { tanggal: "25 May 2020" }, expected: { tanggal: "25 May 2020" } },
{ name: "sanitize: single digit tanggal 4 April 2026 -> 04 April 2026", input: { tanggal: "4 April 2026" }, expected: { tanggal: "04 April 2026" } },
// tanggal invalid // tanggal invalid
{ name: "sanitize: tanggal bad month Hv -> Not Found", input: { tanggal: "25 Hv 2024" }, expected: { tanggal: "Not Found" } }, { name: "sanitize: tanggal bad month Hv -> Not Found", input: { tanggal: "25 Hv 2024" }, expected: { tanggal: "Not Found" } },
{ name: "sanitize: tanggal as number 0011770 -> Not Found", input: { tanggal: "0011770" }, expected: { tanggal: "Not Found" } }, { name: "sanitize: tanggal as number 0011770 -> Not Found", input: { tanggal: "0011770" }, expected: { tanggal: "Not Found" } },
+116 -26
View File
@@ -26,7 +26,7 @@ function cleanAndFormatPO(raw: string, currentYearLastTwo: string): string {
// Pattern 1: Any form with at least one slash — PO/26/nnn, F0/20/nnn, PO120/nnn, 07/26/nnn // Pattern 1: Any form with at least one slash — PO/26/nnn, F0/20/nnn, PO120/nnn, 07/26/nnn
// Structure: [PREFIX][optional_noise_digits][/][optional_year_segment][/]?[NUMBER] // Structure: [PREFIX][optional_noise_digits][/][optional_year_segment][/]?[NUMBER]
// We find the LAST slash and take everything after it as the real number // We find the LAST slash and take everything after it as the real number
const withSlash = /^(?:[A-Z0-9]{1,4})[ \t]*[\/\-][ \t]*(?:\d{1,4}[ \t]*[\/\-][ \t]*)?(\d{4,})/i; const withSlash = /^(?:[A-Z0-9]+)[ \t]*[\/\-][ \t]*(?:\d{1,4}[ \t]*[\/\-][ \t]*)?(\d{4,})/i;
const m1 = stripped.match(withSlash); const m1 = stripped.match(withSlash);
if (m1) { if (m1) {
return `PO/${currentYearLastTwo}/${m1[1]}`; return `PO/${currentYearLastTwo}/${m1[1]}`;
@@ -93,7 +93,20 @@ function cleanDateValue(raw: string): string {
const cleaned = raw.trim(); const cleaned = raw.trim();
if (cleaned === "Not Found" || cleaned === "") return "Not Found"; if (cleaned === "Not Found" || cleaned === "") return "Not Found";
const today = new Date(); // Try unified regex first
const unifiedPattern = /(\d{1,2})[ \t\-\/]*(jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|maret|apr(?:il)?|may|mei|jun(?:[ei])?|jul(?:[ii])?|aug(?:ustus)?|agt|ags|sep(?:tember)?|oct(?:ober)?|okt|nov(?:ember)?|nop(?:ember)?|dec(?:ember)?|des(?:ember)?)[ \t\-\/]*(20\d{2})/i;
const match = cleaned.match(unifiedPattern);
if (match) {
const day = parseInt(match[1], 10);
const monthKey = match[2].toLowerCase();
const year = parseInt(match[3], 10);
const month = MONTHS_MAP[monthKey] || monthKey;
if (day >= 1 && day <= 31 && year >= 2010 && year <= 2035) {
const paddedDay = day.toString().padStart(2, '0');
return `${paddedDay} ${month} ${year}`;
}
}
let day: number | null = null; let day: number | null = null;
let monthStr: string | null = null; let monthStr: string | null = null;
let year: number | null = null; let year: number | null = null;
@@ -119,12 +132,14 @@ function cleanDateValue(raw: string): string {
} }
} }
if (!monthStr) return "Not Found";
// 3. Try to find 1 or 2 digit day (not part of the year) // 3. Try to find 1 or 2 digit day (not part of the year)
let textForDay = cleaned; let textForDay = cleaned;
if (year) { if (year) {
textForDay = textForDay.replace(year.toString(), ""); textForDay = textForDay.replace(year.toString(), "");
} }
const dayMatches = textForDay.match(/\b(\d{1,2})\b/g); const dayMatches = textForDay.match(/\b(\d{1,2})\b/g) || textForDay.match(/(\d{1,2})/g);
if (dayMatches) { if (dayMatches) {
for (const matchStr of dayMatches) { for (const matchStr of dayMatches) {
const parsedDay = parseInt(matchStr, 10); const parsedDay = parseInt(matchStr, 10);
@@ -135,17 +150,12 @@ function cleanDateValue(raw: string): string {
} }
} }
// 4. Fallback fill-in from current date if (day !== null && year !== null) {
const currentYear = today.getFullYear(); const paddedDay = day.toString().padStart(2, '0');
const currentMonthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; return `${paddedDay} ${monthStr} ${year}`;
const currentMonth = currentMonthNames[today.getMonth()]; }
const currentDay = today.getDate();
const finalDay = day !== null ? day : currentDay; return "Not Found";
const finalMonth = monthStr !== null ? monthStr : currentMonth;
const finalYear = year !== null ? year : currentYear;
return `${finalDay} ${finalMonth} ${finalYear}`;
} }
export function parseDOMetadata(markdown: string) { export function parseDOMetadata(markdown: string) {
@@ -317,14 +327,20 @@ export function parseDOMetadata(markdown: string) {
metadata.tanggal = dateMatch[0]; metadata.tanggal = dateMatch[0];
} }
// Only shift values if the PO field is shifted (i.e. PO field is a 10-digit number instead of PO format).
// If the PO field has a valid PO format (e.g. PO/26/...), PO and DO are in correct places and not shifted.
const isPoValid = /^PO\/[A-Z0-9\-\/]+\b/i.test(originalPO);
if (!isPoValid) {
// 2. Real SO is the value that was matched under No. DO // 2. Real SO is the value that was matched under No. DO
if (/^\d{10}$/.test(originalDO)) { if (/^\d{10}$/.test(originalDO)) {
metadata.noSO = originalDO; metadata.noSO = originalDO;
} else if (metadata.noSO === "Not Found" || isShortSO || isDateInSO) { } else if (metadata.noSO === "Not Found" || isShortSO || isDateInSO) {
const tenDigitRegex = /\b\d{10}\b/g; const tenDigitRegex = /\b\d{10}\b/g;
const m = cleanMarkdown.match(tenDigitRegex); const m = cleanMarkdown.match(tenDigitRegex);
if (m && m.length > 0) { const cleanMatches = m ? m.filter(val => !originalPO.includes(val)) : [];
metadata.noSO = m[0]; if (cleanMatches.length > 0) {
metadata.noSO = cleanMatches[0];
} }
} }
@@ -334,8 +350,15 @@ export function parseDOMetadata(markdown: string) {
} else if (metadata.noDO === "Not Found" || isShortSO || isDateInSO) { } else if (metadata.noDO === "Not Found" || isShortSO || isDateInSO) {
const tenDigitRegex = /\b\d{10}\b/g; const tenDigitRegex = /\b\d{10}\b/g;
const m = cleanMarkdown.match(tenDigitRegex); const m = cleanMarkdown.match(tenDigitRegex);
if (m && m.length > 1) { const cleanMatches = m ? m.filter(val => !originalPO.includes(val)) : [];
metadata.noDO = m[1]; if (cleanMatches.length > 1) {
metadata.noDO = cleanMatches[1];
}
}
} else {
// PO is valid and NOT shifted. SO was just empty or noise.
if (isShortSO || isDateInSO) {
metadata.noSO = "Not Found";
} }
} }
@@ -420,7 +443,11 @@ export function parseDOMetadata(markdown: string) {
metadata.noDO = cleanFinalValue(metadata.noDO); metadata.noDO = cleanFinalValue(metadata.noDO);
// Extract PO year dynamically from parsed document date (or default to current year if date not found) // Extract PO year dynamically from parsed document date (or default to current year if date not found)
const docYearLastTwo = getYearFromDate(metadata.tanggal); const currentYY = new Date().getFullYear().toString().slice(-2);
let docYearLastTwo = getYearFromDate(metadata.tanggal);
if (docYearLastTwo !== currentYY) {
docYearLastTwo = currentYY;
}
metadata.noPO = cleanAndFormatPO(cleanFinalValue(metadata.noPO), docYearLastTwo); metadata.noPO = cleanAndFormatPO(cleanFinalValue(metadata.noPO), docYearLastTwo);
// Parse HTML tables for items // Parse HTML tables for items
@@ -536,6 +563,45 @@ export function parseDOMetadata(markdown: string) {
} }
if (isItemsTable) { if (isItemsTable) {
// Check for SKU column downward shift of 1 row:
// - First data row (r=1) has empty SKU, but has description
// - A downstream row (shiftBoundaryRow) has SKU, but description, banyak, jumlah are empty
if (numRows > 2 && kIdx !== -1 && nIdx !== -1) {
const firstRowSku = (grid[1][kIdx] || "").trim();
const firstRowDesc = (grid[1][nIdx] || "").trim();
if (firstRowSku === "" && firstRowDesc !== "") {
let shiftBoundaryRow = -1;
for (let r = 2; r < numRows; r++) {
const skuVal = (grid[r][kIdx] || "").trim();
let othersEmpty = true;
for (let c = 0; c < maxCols; c++) {
if (c !== kIdx) {
const val = (grid[r][c] || "").trim();
if (val !== "") {
othersEmpty = false;
break;
}
}
}
if (skuVal !== "" && /^\d{8}$/.test(skuVal) && othersEmpty) {
shiftBoundaryRow = r;
break;
}
}
if (shiftBoundaryRow !== -1) {
console.log(`Detected shifted SKU column in table. Shift boundary row: ${shiftBoundaryRow}. Correcting alignment...`);
for (let r = 1; r < shiftBoundaryRow; r++) {
grid[r][kIdx] = grid[r + 1][kIdx];
}
grid[shiftBoundaryRow][kIdx] = "";
}
}
}
for (let r = 1; r < numRows; r++) { for (let r = 1; r < numRows; r++) {
const cells = grid[r]; const cells = grid[r];
const kodeCell = cells[kIdx] || ""; const kodeCell = cells[kIdx] || "";
@@ -544,7 +610,7 @@ export function parseDOMetadata(markdown: string) {
let jumlahCell = ""; let jumlahCell = "";
// Check if there is an extra column before banyak that we should merge with banyak // Check if there is an extra column before banyak that we should merge with banyak
if (bIdx > 2 && bIdx - 1 !== nIdx && bIdx - 1 !== kIdx) { if (bIdx > 2 && bIdx - 1 !== nIdx && bIdx - 1 !== kIdx && headerCells[bIdx - 1] !== headerCells[nIdx]) {
const qtyCell = cells[bIdx - 1] || ""; const qtyCell = cells[bIdx - 1] || "";
const unitCell = cells[bIdx] || ""; const unitCell = cells[bIdx] || "";
banyakCell = `${qtyCell} ${unitCell}`.trim(); banyakCell = `${qtyCell} ${unitCell}`.trim();
@@ -612,6 +678,11 @@ export function parseDOMetadata(markdown: string) {
// Clean checkmarks and extra spaces from banyak // Clean checkmarks and extra spaces from banyak
b = b.replace(/[✓☑]/g, "").replace(/\s+/g, " ").trim(); b = b.replace(/[✓☑]/g, "").replace(/\s+/g, " ").trim();
b = b.replace(/\b80[xX]\b/g, "BOX")
.replace(/\bB0[xX]\b/g, "BOX")
.replace(/\b8[aA][gG]\b/g, "BAG")
.replace(/\b[pP][aA][iI]\b/g, "PAC")
.trim();
// Fallback for Banyak if empty or purely alphabetical unit // Fallback for Banyak if empty or purely alphabetical unit
if (!b) { if (!b) {
@@ -626,11 +697,21 @@ export function parseDOMetadata(markdown: string) {
const name = n.toLowerCase(); const name = n.toLowerCase();
if (code === "11310024" || name.includes("griller")) { if (code === "11310024" || name.includes("griller")) {
b = `${b} KRG`; b = `${b} KRG`;
} else if (code === "11640053" || name.includes("bone in leg") || name.includes("pack")) { } else if (code === "11640053" || name.includes("bone in leg")) {
b = `${b} BAG`; b = `${b} BAG`;
} else if (code.startsWith("21") || code.startsWith("12") || name.includes("nasi") || name.includes("rice") || name.includes("nugget") || name.includes("sosis") || name.includes("sausage") || name.includes("fiesta") || name.includes("champ")) {
b = `${b} BOX`;
} }
} }
// Clean checkmarks and extra spaces from jumlah
let cleanJ = j.replace(/[✓☑]/g, "").replace(/\s+/g, " ").trim();
cleanJ = cleanJ.replace(/\b80[xX]\b/g, "BOX")
.replace(/\bB0[xX]\b/g, "BOX")
.replace(/\b8[aA][gG]\b/g, "BAG")
.replace(/\b[pP][aA][iI]\b/g, "PAC")
.trim();
const cleanKode = k.trim(); const cleanKode = k.trim();
const cleanNama = n.trim(); const cleanNama = n.trim();
if (cleanKode === "" && cleanNama === "") { if (cleanKode === "" && cleanNama === "") {
@@ -641,7 +722,7 @@ export function parseDOMetadata(markdown: string) {
kodeBarang: k, kodeBarang: k,
namaBarang: n, namaBarang: n,
banyak: b, banyak: b,
jumlah: j jumlah: cleanJ
}); });
} }
} }
@@ -671,6 +752,11 @@ export function extractPlatTruk(text: string): string {
]; ];
const isValidPrefix = (p: string) => arrayPlat.includes(p.toUpperCase()); const isValidPrefix = (p: string) => arrayPlat.includes(p.toUpperCase());
const FORBIDDEN_SUFFIXES = [
"BOX", "KRG", "BAG", "PAC", "KG", "PC", "PCS", "LGT", "K", "EKR", "LTR", "BKS",
"IDN", "WH", "PM", "TTD", "CPI", "HO", "NO", "GR", "G"
];
const isForbiddenSuffix = (s: string) => FORBIDDEN_SUFFIXES.includes(s.toUpperCase());
// 1. Look for explicit labels: No. Polisi, No. Pol, No. Polisi:, No. Kendaraan, Plat No, Plat, Truck No., etc. // 1. Look for explicit labels: No. Polisi, No. Pol, No. Polisi:, No. Kendaraan, Plat No, Plat, Truck No., etc.
const labelRegex = /(?:No\.?\s*(?:Polisi|Pol|Kendaraan|Mobil|Truck|Pol\.?)|Plat(?:\s*No)?|Truck\s*No\.?)\s*[:\-.]?\s*\b([A-Z]{1,2})[ \t\-]*(\d{1,4})[ \t\-]*([A-Z]{1,3})\b/i; const labelRegex = /(?:No\.?\s*(?:Polisi|Pol|Kendaraan|Mobil|Truck|Pol\.?)|Plat(?:\s*No)?|Truck\s*No\.?)\s*[:\-.]?\s*\b([A-Z]{1,2})[ \t\-]*(\d{1,4})[ \t\-]*([A-Z]{1,3})\b/i;
@@ -679,7 +765,7 @@ export function extractPlatTruk(text: string): string {
const prefix = labelMatch[1].toUpperCase(); const prefix = labelMatch[1].toUpperCase();
const num = labelMatch[2]; const num = labelMatch[2];
const suffix = labelMatch[3].toUpperCase(); const suffix = labelMatch[3].toUpperCase();
if (isValidPrefix(prefix)) { if (isValidPrefix(prefix) && !isForbiddenSuffix(suffix)) {
return `${prefix} ${num} ${suffix}`; return `${prefix} ${num} ${suffix}`;
} }
} }
@@ -692,7 +778,7 @@ export function extractPlatTruk(text: string): string {
const num = match[2]; const num = match[2];
const suffix = match[3].toUpperCase(); const suffix = match[3].toUpperCase();
if (isValidPrefix(prefix)) { if (isValidPrefix(prefix) && !isForbiddenSuffix(suffix)) {
return `${prefix} ${num} ${suffix}`; return `${prefix} ${num} ${suffix}`;
} }
} }
@@ -771,9 +857,10 @@ export function sanitizeParsedMetadata(meta: ReturnType<typeof parseDOMetadata>
const day = parseInt(dm[1], 10); const day = parseInt(dm[1], 10);
const year = parseInt(dm[3], 10); const year = parseInt(dm[3], 10);
if (day >= 1 && day <= 31 && year >= 2010 && year <= currentFullYear + 1) { if (day >= 1 && day <= 31 && year >= 2010 && year <= currentFullYear + 1) {
// Valid — normalize capitalization // Valid — normalize capitalization and pad day with leading zero
const month = dm[2].charAt(0).toUpperCase() + dm[2].slice(1).toLowerCase(); const month = dm[2].charAt(0).toUpperCase() + dm[2].slice(1).toLowerCase();
result.tanggal = `${dm[1]} ${month} ${dm[3]}`; const paddedDay = dm[1].padStart(2, '0');
result.tanggal = `${paddedDay} ${month} ${dm[3]}`;
} else { } else {
result.tanggal = "Not Found"; result.tanggal = "Not Found";
} }
@@ -783,7 +870,10 @@ export function sanitizeParsedMetadata(meta: ReturnType<typeof parseDOMetadata>
// --- noPO --- // --- noPO ---
// Get year from parsed date (or default to current year if date not found) // Get year from parsed date (or default to current year if date not found)
const docYY = result.tanggal && result.tanggal !== "Not Found" ? getYearFromDate(result.tanggal) : currentYY; let docYY = result.tanggal && result.tanggal !== "Not Found" ? getYearFromDate(result.tanggal) : currentYY;
if (docYY !== currentYY) {
docYY = currentYY;
}
// --- noPO --- // --- noPO ---
// Must match PO/YY/NNNN+ where YY is the document-specific year segment, NNNN = 4+ digits // Must match PO/YY/NNNN+ where YY is the document-specific year segment, NNNN = 4+ digits
Binary file not shown.
+46
View File
@@ -0,0 +1,46 @@
# AI OCR Accuracy & Performance Report
This report summarizes the comparison of **AI OCR Extraction (Layer 3 Final)** against the **Manual Ground Truth Labels** across all **37 test images**.
---
## 📊 Summary Accuracy
| Field / Area | Total Checks | Matches | Mismatches | Accuracy (%) |
| :--- | :---: | :---: | :---: | :---: |
| **PO Number** | 37 | 33 | 4 | **89.19%** |
| **SO Number** | 37 | 31 | 6 | **83.78%** |
| **DO Number** | 37 | 32 | 5 | **86.49%** |
| **Date** | 37 | 20 | 17 | **54.05%** |
| **Plat Nomor** | 37 | 21 | 16 | **56.76%** |
| **Customer Name** | 37 | 37 | 0 | **100.00%** |
| **Store Name** | 37 | 11 | 26 | **29.73%** |
| **Alamat** | 37 | 12 | 25 | **32.43%** |
| **Item SKU** | 127 | 119 | 8 | **93.70%** |
| **Item Banyak (Qty Pkg)** | 127 | 100 | 27 | **78.74%** |
| **Item Jumlah (Qty Unit)**| 127 | 92 | 35 | **72.44%** |
| **OVERALL TOTAL** | **677** | **508** | **169** | **75.04%** |
*Detailed, color-coded comparison results (including side-by-side matches/mismatches) are available in the generated Excel sheet: [sources/comparison_report.xlsx](file:///d:/Client/Data%20Bisnis%20Solusi/app-pfm-ocr-v2/backend/sources/comparison_report.xlsx).*
---
## 🔍 Key Findings & Analysis
### 🟢 What is Performing Well (High Accuracy)
1. **Customer Name (100.00%)**: Every customer name field matches perfectly, as it is standard and usually well-printed.
2. **Item SKU (93.70%)**: Outstanding performance on SKU extraction. This is boosted by the **SKU master list validation** (Levenshtein score threshold check), which dynamically corrects OCR recognition noise into actual valid database SKUs.
3. **DO, PO, and SO Numbers (83.78% - 89.19%)**: High accuracy for document tracking numbers, which is crucial for database consistency and mapping.
### 🟡 Moderate Performance (Needs Tuning)
1. **Item Banyak (78.74%) & Item Jumlah (72.44%)**: Quantity parsing sometimes gets thrown off by OCR capturing checkmarks (`✓`, `✔`) or adjacent unit annotations (e.g. `PC`, `PAC`, `KG`) next to digits, leading to slight string mismatches.
### 🔴 Opportunities for Improvement (Low Accuracy)
1. **Store Name (29.73%) & Alamat (32.43%)**:
- *Why it's low*: The manual labels for address and store name contain highly specific strings, while the AI OCR might extract partial address segments, omit specific keywords (like "DKI AREA", "KANTIN CPI"), or extract slightly different abbreviations.
- *Recommendation*: Introduce a fuzzy store-name and address resolution map against the database entries (using Levenshtein distance on store aliases) to dynamically map extracted text to clean master values.
2. **Date (54.05%)**:
- *Why it's low*: Differences in format normalization (e.g., raw text "23 June 2026" vs expected normalized representations, or OCR misreading text dates like "15 May 2026").
- *Recommendation*: Enhance `sanitizeParsedMetadata` in [parser.ts](file:///d:/Client/Data%20Bisnis%20Solusi/app-pfm-ocr-v2/backend/pfm-web-app/src/utils/parser.ts) to parse multiple date format variations into a unified target format.
3. **Plat Nomor (56.76%)**:
- *Why it's low*: License plates on delivery orders are often stamped, handwritten, or placed in odd margins, which makes clean extraction difficult.
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
Binary file not shown.
-230
View File
@@ -1,230 +0,0 @@
PT. Primafood International-PFM Jakarta
Rekap SKU Aktif CPI Cikande
Per April 2026
No No SKU Nama Item Standar Jumlah Berat per kemasan (kg) Isi per outer (kg) Isi per outer (pac) Jenis Outer (karung/box)
1 11110059 CEKER BERKUKU FROZEN PACK 1 KG(*) bag 1 10 10 Box
2 11110074 CEKER 1 KG FROZEN (20 PAC/KARUNG)(*) PAC 1 20 20 Karung
3 11140051 AMPELA FROZEN PACK 1 KG(*) kg 1 10 10 Box
4 11140062 AMPELA 1 KG FROZEN (20 PAC/KARUNG)(*) PAC 1 20 20 Karung
5 11150052 HATI FROZEN PACK 1 KG(*) kg 1 10 10 Karung
6 11150055 JANTUNG FROZEN PACK 1 KG(*) kg 1 10 10 Karung
7 11150064 HATI 1 KG FROZEN (20 PAC/KARUNG)(*) PAC 1 20 20 Karung
8 11150065 JANTUNG 1 KG FROZEN (20 PAC/KARUNG)(*) PAC 1 20 20 Karung
9 11310012 AYAM SIZE 0 (0.6-0.7)KG(*) PC 0.65 26 40 Karung
10 11310013 AYAM SIZE1 FROZEN (0.75-0.8) KG(*) PC 0.75 22.5 30 Karung
11 11310014 AYAM SIZE 2 FROZEN (0.8-0.9)KG(*) PC 0.85 17 20 Karung
12 11310016 AYAM SIZE Z PR FROZ(*) PC 0.58 23.2 40 Karung
13 11310017 AYAM SIZE 0 PR FROZEN(*) PC 0.65 26 40 Karung
14 11310018 AYAM SIZE 1 PR FROZEN(*) PC 0.75 22.5 30 Karung
15 11310019 AYAM SIZE 2 PR FROZEN(*) PC 0.85 17 20 Karung
16 11310021 AYAM SIZE BESAR (B) FROZ (1-1.1)KG/PC(*) PC 1.05 21 20 Karung
17 11310022 AYAM SIZE A PR (0.9-1)KG/PC(*) PC 0.95 19 20 Karung
18 11310024 AYAM SIZE A FROZEN (0.9-1)KG/PC(*) PC 0.95 19 20 Karung
19 11310025 AYAM SIZE SUPER (C) FROZ(1.1-1.2)KG/ PC(*) PC 1.15 23 20 Karung
20 11310026 AYAM SIZE JUMBO (D) FROZ (1.2- 1.3)KG/PC(*) pc 1.25 20 16 Karung
21 11410043 PARTING 10 SIZE D FRESH BENSU 1.25 KG/PAC(*) PAC 1.25 12.5 10 Bag
22 11420055 PARTING 12 ALL SIZE FROZ/PAC(*) PAC 1 10 10 Box
23 11600053 BONELESS LEG FROZEN 1 KG(*) kg 1 10 10 Box
24 11620056 SBL (FILLET PAHA) 1 KG(*) kg 1 10 10 Box
25 11640053 PAHA UTUH (1 KG)(*) PAC 1 10 10 Box
26 11650053 PAHA ATAS 1 KG(*) kg 1 10 10 Box
27 11660050 PAHA BAWAH (1 KG)(*) PAC 1 10 10 Box
28 11690053 SBB (FILLET DADA )1 KG(*) kg 1 10 10 Box
29 11690081 SBB JUMBO FZ (2.0 - 2.2 KG/PAC)(*) PAC 2 20 10 Karung
30 11710051 DADA UTUH (1 KG)(*) kg 1 10 10 Box
31 11720055 FULL WING FROZ PACK 1 KG(*) kg 1 10 10 Box
32 11730050 MIDDLE WING FROZ PACK 1 KG(*) PAC 1 10 10 Box
33 11750050 FILLET MITRA 1 KG(*) KG 1 10 10 Box
34 11840002 AYAM JANTAN BKKL SZ 0 (600-700) GR/PC(*) PC 0 0 0 Karung
35 11959937 SATE AYAM FRESHMART 360 GR (PAC) PAC 0.36 7.2 20 Box
36 12010111 FIESTA CRISPY BUBBLE 400 GR/PAC PAC 0.4 4.8 12 Box
37 12010112 FIESTA CHICKEN NUGGET 400 GR/PAC PAC 0.4 4.8 12 Box
38 12010113 FIESTA CHICKEN NUGGET 200 GR/PAC PAC 0.2 4.8 24 Box
39 12010115 FIESTA NUGGET ZOO 400 GR/PAC PAC 0.4 4.8 12 Box
40 12010116 FIESTA NUGGET DINO 400 GR/PAC PAC 0.4 4.8 12 Box
41 12010117 FIESTA NUGGET HAPPY STAR 400 GR/PAC PAC 0.4 4.8 12 Box
42 12010119 FIESTA NUGGET CHEESE 123 400 GR/PAC PAC 0.4 4.8 12 Box
43 12010121 FIESTA NUGGET PIZZABC 400 GR/PAC PAC 0.4 4.8 12 Box
44 12010122 FIESTA CHEESY LOVER 400 GR/PAC PAC 0.4 4.8 12 Box
45 12010123 FIESTA GARLIC CHEESE 400 GR/PAC PAC 0.4 4.8 12 Box
46 12010124 FIESTA CHEESY CHIC W/BROCCOLI 400 GR/PAC PAC 0.4 4.8 12 Box
47 12010127 FIESTA SPICY NUGGET 400 GR/PAC PAC 0.4 4.8 12 Box
48 12010128 FIESTA VOLCANO CHEESE 400 GR/PAC PAC 0.4 4.8 12 Box
49 12010129 FIESTA CHEESY BOMBS CHICKEN NUGGET 400 GR PAC 0.4 4.8 12 Box
50 12010402 GOLDEN FIESTA NUGGET W/PINEAPPLE SAUCE 500 GR PAC 0.5 5 10 Box
51 12010509 CHAMP CRUNCHY NUGGET 450 GR/PAC PAC 0.45 4.5 10 Box
52 12010510 CHAMP NUGGET AYAM 225 GR/PAC PAC 0.225 4.95 22 Box
53 12010511 CHAMP NUGGET AYAM 450 GR/PAC PAC 0.45 5.4 12 Box
54 12010512 CHAMP NUGGET AYAM 900 GR/PAC PAC 0.9 5.4 6 Box
55 12010513 CHAMP NUGGET ABC KOMBINASI 225 GR/PAC PAC 0.225 4.95 22 Box
56 12010514 CHAMP NUGGET ABC KOMBINASI 450 GR/PAC PAC 0.45 5.4 12 Box
57 12010515 CHAMP KOIN KOMBINASI 450 GR/PAC PAC 0.45 5.4 12 Box
58 12010516 CHAMP KOIN KOMBINASI 200 GR/PAC PAC 0.2 6 30 Box
59 12010517 CHAMP NUGGET STICK 225 GR/PAC PAC 0.225 4.95 22 Box
60 12010518 CHAMP NUGGET STICK 450 GR/PAC PAC 0.45 5.4 12 Box
61 12010519 CHAMP NUGGET STICK 900 GR/PAC PAC 0.9 5.4 6 Box
62 12010520 CHAMP CHICKEN NUGGET BENTUK 123 450 GR/PAC PAC 0.45 5.4 12 Box
63 12010521 CHAMP NUGGET HOTZZ LEVEL 5 450 GR/PAC PAC 0.45 5.4 12 Box
64 12010606 CHAMP CRUNCHY NUGGET 225 GR/PAC PAC 0.225 5.625 25 Box
65 12010707 CHAMP MITRA NUGGET COIN 200 GR (NEW) PAC 0.2 6 30 Box
66 12010801 OKEY NUGGET 500GR PAC 0.5 5 10 Box
67 12012201 ASIMO NUGGET KOMBINASI 500 GR/PAC PAC 0.5 5 10 Box
68 12012202 ASIMO NUGGET KOMBINASI 1 KG/PAC PAC 1 5 5 Box
69 12012203 ASIMO NUGGET KOMBINASI 250 GR/PAC PAC 0.25 5 20 Box
70 12012501 AKUMO CHICKEN NAGET 250 GR PAC 0.25 5 20 Box
71 12012502 AKUMO CHICKEN NUGGET 500 GR PAC 0.5 5 10 Box
72 12012503 AKUMO CHICKEN NUGGET 1000 GR PAC 1 5 5 Box
73 12012504 AKUMO COIN 200 GR/PAC pack 0.2 6 30 Box
74 12012505 AKUMO KOIN 400 GR/PAC PAC 0.4 6 15 Box
75 12020102 FIESTA SPICY WING 400 GR/PAC PAC 0.4 4.8 12 Box
76 12020401 GOLDEN FIESTA SP WING 500 GR PAC 0.5 5 10 Box
77 12030101 FIESTA STIKIE 400 GR/PAC PAC 0.4 4.8 12 Box
78 12030102 FIESTA STIKIE 200 GR/PAC PAC 0.2 4.8 24 Box
79 12030403 GOLDEN FIESTA STIKIE W/ SWEET CHILLI SAUCE 500GR PAC 0.5 5 10 Box
80 12030801 OKEY STICK 1000 GR PAC 1 5 5 Box
81 12030802 OKEY STICK 500GR PAC 0.5 5 10 Box
82 12032201 ASIMO STICK KOMBINASI 500 GR/PAC PAC 0.5 5 10 Box
83 12032202 ASIMO STICK KOMBINASI 1000 GR/PAC PAC 1 5 5 Box
84 12032203 ASIMO STIK KOMBINASI 250 GR/PAC PAC 0.25 5 20 Box
85 12032501 AKUMO CHICKEN STICK 250 GR PAC 0.25 5 20 Box
86 12032502 AKUMO CHICKEN STIK 500 GR PAC 0.5 5 10 Box
87 12032503 AKUMO CHICKEN STICK 1000 GR PAC 1 5 5 Box
88 12040101 FIESTA SCHNITZEL 400 GR/PAC PAC 0.4 4.8 12 Box
89 12040102 FIESTA CRISPY BUBBLE KATSU 400 GR/PAC PAC 0.4 4.8 12 Box
90 12040404 GOLDEN FIESTA CORDON BLEU BBQ SAUCE 500 GR PAC 0.5 5 10 Box
91 12040406 GOLDEN FIESTA KATSU W/CHEESE SAUCE 500 GR/PAC PAC 0.5 5 10 Box
92 12050103 FIESTA FRIED CHICKEN 400 GR/PAC PAC 0.4 4.8 12 Box
93 12050104 FIESTA HOT & CRISPY FRIED CHICKEN 400 GR/PAC PAC 0.4 4.8 12 Box
94 12050401 GOLDEN FIESTA CRISPY WING W/SP GLAZ SC 500 GR/PAC PAC 0.5 5 10 Box
95 12060103 FIESTA KARAGE 200 GR/PAC PAC 0.2 4.8 24 Box
96 12060104 FIESTA KARAGE 400 GR/PAC PAC 0.4 4.8 12 Box
97 12060105 FIESTA SPICY KARAGE 400 GR/PAC PAC 0.4 4.8 12 Box
98 12060402 GOLDEN FIESTA KARAGE CHILI SAUCE 500GR PAC 0.5 5 10 Box
99 12070101 FIESTA POK-POK 400 GR/PAC (NEW) PAC 0.4 4.8 12 Box
100 12080101 FIESTA SPICY CHICK 400 GR/PAC PAC 0.4 4.8 12 Box
101 12130102 FIESTA CRISPY BURGER 360 GR (NEW) PAC 0.36 7.2 20 Box
102 12130504 CHAMP BURGER 315 GR (NEW) PAC 0.315 6.3 20 Box
103 12140105 FIESTA CHICK TOFU 400 GR/PAC PAC 0.4 4.8 12 Box
104 12150201 FIESTA DS CRISPY CRUNCH 300 GR/PAC PAC 0.3 3 10 Box
105 12150501 CHAMP CRUNCHY HOTZZ 300 GR/PAC PAC 0.3 3 10 Box
106 12190103 FIESTA DELISTRIPE 400 GR/PAC PAC 0.4 4.8 12 Box
107 12240102 FIESTA CHEESY ITALIAN R/BITES 400 GR/PAC PAC 0.4 4.8 12 Box
108 12240103 FIESTA YAKINIKU R/BITES 400 GR/PAC PAC 0.4 4.8 12 Box
109 13010101 FIESTA CHICK SSG 300 GR PAC 0.3 6 20 Box
110 13010102 FIESTA CHICK SSG 500 GR PAC 0.5 5 10 Box
111 13010103 FIESTA CHICK SSG 200 GR/PAC PAC 0.2 5 25 Box
112 13010111 FIESTA SOSIS BRATWURST 300 GR PAC 0.3 6 20 Box
113 13010112 FIESTA CHEESE SSG 300 GR PAC 0.3 6 20 Box
114 13010113 FIESTA SOSIS CURRYWURST 300 GR PAC 0.3 6 20 Box
115 13010114 FIESTA SSG BOCKWURST 300GR PAC 0.3 6 20 Box
116 13010115 FIESTA SSG WIENER 300GR PAC 0.3 6 20 Box
117 13010116 FIESTA SSG ORIGINAL 300 GR PAC 0.3 6 20 Box
118 13010117 FIESTA SSG FRANKFURTER 300GR PAC 0.3 6 20 Box
119 13010118 FIESTA RTG SSG 65 GR/PAC PAC 0.065 4.68 72 Box
120 13010119 FIESTA RTG C/SPICY KOREAN 60 GR/PAC PAC 0.06 4.32 72 Box
121 13010120 FIESTA RTG C/CHEESY MELTS 65 GR/PAC PAC 0.065 4.68 72 Box
122 13010122 FIESTA RTG SAUSAGE WITH HOT LAVA 60G PAC 0.06 4.32 72 Box
123 13010123 FIESTA RTG SAUSAGE WITH CHEESE LAVA 60G PAC 0.06 4.32 72 Box
124 13010124 FIESTA RTG SAUSAGE HICKORY SAUCE 60GR PAC 0.06 4.32 72 Box
125 13010125 FIESTA RTG SAUSAGE WITH MENTAI LAVA 60GR PAC 0.06 4.32 72 Box
126 13010510 CHAMP CHICK SSG 75 GR PAC 0.075 6 80 Box
127 13010513 CHAMP CHICK SSG 375 GR PAC 0.375 5.625 15 Box
128 13010514 CHAMP CHICK SSG 1000 GR PAC 1 10 10 Box
129 13010518 CHAMP SSG JUMBO BAKAR 500 GR/PAC PAC 0.5 5 10 Box
130 13010519 CHAMP SSG BAKAR MINI 500 GR/PAC-INACT PAC 0.5 5 10 Box
131 13010521 CHAMP CHICK SSG 150 GR/PAC (NEW) PAC 0.15 6.75 45 Box
132 13010523 CHAMP CHICK SSG AYAM MADU 300 GR/PAC PAC 0.3 6 20 Box
133 13010524 CHAMP SSG JUMBO BAKAR 500 GR/PAC (NEW) PAC 0.5 5 10 Box
134 13010525 CHAMP SSG BAKAR MINI 500 GR/PAC (NEW) PAC 0.5 5 10 Box
135 13010809 OKEY CHICK SSG 500GR-INACT PAC 0.5 10 20 Box
136 13010815 OKEY SSG BAKAR JUMBO 500 GR/PAC (NEW) PAC 0.5 5 10 Box
137 13010816 OKEY SSG BAKAR MINI 500 GR/PAC (NEW) PAC 0.5 5 10 Box
138 13010817 OKEY SSG CHICK KOMBINASI 500 GR (EXTRA1) PAC 0.5 10 20 Box
139 13010818 OKEY SSG CHICK KOMBINASI 1 KG (EXTR$A2) PAC 1 10 10 Box
140 13012205 ASIMO SOSIS AYAM KOMBINASI 375 GR (PAC) PAC 0.375 5.625 15 Box
141 13012206 ASIMO SOSIS AYAM KOMBINASI 500 GR PAC 0.5 10 20 Box
142 13012207 ASIMO SOSIS AYAM KOMBINASI 750 GR PAC 0.75 6 8 Box
143 13012208 ASIMO SOSIS AYAM KOMBINASI 1000 GR PAC 1 10 10 Box
144 13012209 ASIMO SSG CHICK KOMBINASI 500 GR (EXTRA1) PAC 0.5 10 20 Box
145 13012210 ASIMO SSG CHICK KOMBINASI 1 KG (EXTR$A2) PAC 1 10 10 Box
146 13030101 FIESTA CHICK MEAT BALL 300 GR PAC 0.3 6 20 Box
147 13030102 FIESTA CHICK MEATBALL 500 GR PAC 0.5 5 10 Box
148 13030501 CHAMP CHICK MEATBALL 200 GR PAC 0.2 5 25 Box
149 13030502 CHAMP CHICK MEATBALL 500 GR PAC 0.5 5 10 Box
150 13050101 FIESTA SCB 250 GR PAC 0.25 5 20 Box
151 13050105 FIESTA CHICKEN SLICE 300 GR PAC 0.3 6 20 Box
152 13050106 FIESTA BEEF SLICE 300 GR PAC 0.3 6 20 Box
153 13070501 CHAMP BEEF SSG SERBAGUNA 150 GR PAC 0.15 6.75 45 Box
154 13070502 CHAMP BEEF SSG SERBAGUNA 375GR PAC 0.375 9 24 Box
155 13070505 CHAMP BEEF SSG GORENG 375 GR PAC 0.375 5.625 15 Box
156 13070506 CHAMP FRANKFURTER SSG 375GR PAC 0.375 5.625 15 Box
157 13100512 CHAMP CHICK SSG S/SANTAP ORIG 546GR (CAN) can 0.546 3.276 6 Box
158 13110504 CHAMP BEEF BALL 500GR PAC 0.5 5 10 Box
159 13170510 CHAMP BEEF BBQ SSG S/SANTAP 546GR (CAN) can 0.546 3.276 6 Box
160 15010101 FIESTA SHOESTRING 500 GR PAC 0.5 6 12 Box
161 15010102 FIESTA SHOESTRING 1000 GR PAC 1 12 12 Box
162 15010107 FIESTA FRENCH F SHOESTRING INSTITUSI 2KG PAC 2 12 6 Box
163 15020101 FIESTA STRAIGHT CUT 500 GR PAC 0.5 6 12 Box
164 15020102 FIESTA STRAIGHT CUT 1000 GR PAC 1 12 12 Box
165 15030101 FIESTA CRINKLE CUT 500 GR PAC 0.5 6 12 Box
166 15030102 FIESTA CRINKLE CUT 1000 GR PAC 1 12 12 Box
167 15040101 FIESTA BATTER COATED 500 GR PAC 0.5 6 12 Box
168 15040102 FIESTA BATTER COATED 1000 GR PAC 1 12 12 Box
169 16060103 FIESTA CHICK SIOMAY 900GR PAC 0.9 10.8 12 Box
170 16060113 FIESTA CHICK SIOMAY 180GR (NEW) PAC 0.18 5.4 30 Box
171 16060114 FIESTA GYOZA 180 GR (NEW) PAC 0.18 5.4 30 Box
172 16060119 FIESTA RTG SIOMAY 54 GR/PAC PAC 0.054 3.888 72 Box
173 16060120 FIESTA KEECHO 400 GR/PAC PAC 0.4 4.8 12 Box
174 16060121 FIESTA CHICKEN TOFU 400 GR/PAC (NEW) PAC 0.4 4.8 12 Box
175 16060503 CHAMP CHICK&FISH SIOMAY 180 GR (NEW) PAC 0.18 5.4 30 Box
176 17200109 FIESTA RTS C/TERIYAKI 300GR/PAC PAC 0.3 2.4 8 Box
177 17200110 FIESTA RTS C/RENDANG 300GR/PAC PAC 0.3 2.4 8 Box
178 17200111 FIESTA RTS C/W RUJAK SC 300GR/PAC PAC 0.3 2.4 8 Box
179 17200112 FIESTA RTS C/W S/MATAH 300GR/PAC PAC 0.3 2.4 8 Box
180 17210106 FIESTA RTS B/YAKINIKU 300GR/PAC PAC 0.3 2.4 8 Box
181 17210107 FIESTA RTS B/RENDANG 300GR/PAC PAC 0.3 2.4 8 Box
182 17210108 FIESTA RTS B/BLACKPEPPER 300GR/PAC PAC 0.3 2.4 8 Box
183 17210109 FIESTA RTS B/BULGOGI 300GR/PAC PAC 0.3 2.4 8 Box
184 18050102 FIESTA RTG BAKSO KEJU 60 GR/PAC PAC 0.06 4.32 72 Box
185 18050103 FIESTA RTG BAKSO BAKAR BBQ 60 GR/PAC PAC 0.06 4.32 72 Box
186 18050104 FIESTA RTG BEEF BALL WITH MENTAI LAVA 55GR PAC 0.055 3.96 72 Box
187 18050105 FIESTA RTG BEEF BALL WITH CHEESE LAVA 55GR PAC 0.055 3.96 72 Box
188 20010101 FIESTA CRISPY CRUMBS 200 GR PAC 0.2 4.8 24 Box
189 20010102 FIESTA TP ROTI PUTIH 200 GR PAC 0.2 4.8 24 Box
190 20040101 FIESTA RAMEN BEKU 570 GR/PAC PAC 0.57 6.84 12 Box
191 20120102 FIESTA T/B AYAM GORENG 80 GR PAC 0.08 3.2 40 Box
192 20120105 FIESTA T/B SERBAGUNA  80 GR PAC 0.08 3.2 40 Box
193 20120106 FIESTA T/B KREMES 80 GR PAC 0.08 3.2 40 Box
194 20120115 FIESTA RACIK AYAM GORENG 20 GR/PAC PAC 0.02 1.6 80 Box
195 20120116 FIESTA RACIK NASI GORENG 20 GR/PAC PAC 0.02 1.6 80 Box
196 21000123 FIESTA RICE W/GEPREK CHICKEN 320GR/PAC PAC 0.32 1.92 6 Box
197 21000124 FIESTA RICE W/CHICK RUJAK 320 GR/PAC PAC 0.32 1.92 6 Box
198 21000125 FIESTA RICE W/KOREAN BBQ CHICK 320 GR/PAC PAC 0.32 1.92 6 Box
199 21000126 NEW FIESTA CHICK RENDANG W RICE 320GR (PAC) PAC 0.32 1.92 6 Box
200 21000127 NEW FIESTA CHICK TERIYAKI W RICE 320GR (PAC) PAC 0.32 1.92 6 Box
201 21000128 NEW FIESTA CHICK TANDORI W RICE 320GR (PAC) PAC 0.32 1.92 6 Box
202 21000129 NEW FIESTA RICE W KARAGE&SSS 320GR (PAC) PAC 0.32 1.92 6 Box
203 21000130 NEW FIESTA RICE W/C CHEESE BULDAK 320GR (PAC) PAC 0.32 1.92 6 Box
204 21000131 NEW FIESTA RICE W CHIC CURRY 320GR (PAC) PAC 0.32 1.92 6 Box
205 21000132 NEW FIESTA RICE W CHICK DONBURI 320GR (PAC) PAC 0.32 1.92 6 Box
206 21000133 NEW FIESTA RICE W CHICK SATAY 320GR (PAC) PAC 0.32 1.92 6 Box
207 21000134 NEW FIESTA COCONUT RICE W SPICY CHICK 320GR (PAC) PAC 0.32 1.92 6 Box
208 21000135 NEW FIESTA RICE W POPBITES S/MATAH 320GR (PAC) PAC 0.32 1.92 6 Box
209 21000136 NEW FIESTA TUMERIC W POPBITES 320GR (PAC) PAC 0.32 1.92 6 Box
210 21000137 FIESTA HAINAMESE CHICKEN RICE 320GR (PAC) PAC 0.32 1.92 6 Box
211 21010101 FIESTA TRUFFLE GYUDON 320 GR/PAC PAC 0.32 1.92 6 Box
212 21010102 NEW FIESTA BEEF YAKINIKU W RICE 320GR (PAC) PAC 0.32 1.92 6 Box
213 21010103 NEW FIESTA BEEF BULGOGI W RICE 320GR (PAC) PAC 0.32 1.92 6 Box
214 21010104 NEW FIESTA BEEF RENDANG W RICE 320GR (PAC) PAC 0.32 1.92 6 Box
215 21010105 NEW FIESTA RICE W BEEF BLACKPEPPER 320GR (PAC) PAC 0.32 1.92 6 Box
216 21200107 NEW FIESTA SPAGHETTI CARBONARA 300GR (PAC) PAC 0.3 1.8 6 Box
217 21200108 NEW FIESTA SPAGHETTI CHIC BOLOGNESE 320GR (PAC) PAC 0.32 1.92 6 Box
218 21200109 NEW FIESTA ITALIAN MEATBALL SPAGHETTI 320GR (PAC) PAC 0.32 1.92 6 Box
219 21310103 NEW FIESTA SCB&S/SSG FRIED RICE 320GR (PAC) PAC 0.32 1.92 6 Box
220 21500101 FIESTA CHICK SSG & C. BALL PIZZA 230GR/PAC PAC 0.23 1.15 5 Box
221 21500102 FIESTA CHEESY BEEF PIZZA 230GR/PAC PAC 0.23 1.15 5 Box
222 1188002W PAHA ATAS 25-30 G FZ (*) PAC 1 10 10 Box
223 1195008A RTC CHICKEN KALASAN 400 GR (PAC) PAC 0.41 4.1 10 Box
224 1195008E RTC CHICKEN TERIYAKI 400 GR (PAC) PAC 0.4 3.2 8 Box
225 1195008X RTC CHICKEN SPICY 400 GR (PAC) PAC 0.4 3.2 8 Box
1 PT. Primafood International-PFM Jakarta
2 Rekap SKU Aktif CPI Cikande
3 Per April 2026
4
5 No No SKU Nama Item Standar Jumlah Berat per kemasan (kg) Isi per outer (kg) Isi per outer (pac) Jenis Outer (karung/box)
6 1 11110059 CEKER BERKUKU FROZEN PACK 1 KG(*) bag 1 10 10 Box
7 2 11110074 CEKER 1 KG FROZEN (20 PAC/KARUNG)(*) PAC 1 20 20 Karung
8 3 11140051 AMPELA FROZEN PACK 1 KG(*) kg 1 10 10 Box
9 4 11140062 AMPELA 1 KG FROZEN (20 PAC/KARUNG)(*) PAC 1 20 20 Karung
10 5 11150052 HATI FROZEN PACK 1 KG(*) kg 1 10 10 Karung
11 6 11150055 JANTUNG FROZEN PACK 1 KG(*) kg 1 10 10 Karung
12 7 11150064 HATI 1 KG FROZEN (20 PAC/KARUNG)(*) PAC 1 20 20 Karung
13 8 11150065 JANTUNG 1 KG FROZEN (20 PAC/KARUNG)(*) PAC 1 20 20 Karung
14 9 11310012 AYAM SIZE 0 (0.6-0.7)KG(*) PC 0.65 26 40 Karung
15 10 11310013 AYAM SIZE1 FROZEN (0.75-0.8) KG(*) PC 0.75 22.5 30 Karung
16 11 11310014 AYAM SIZE 2 FROZEN (0.8-0.9)KG(*) PC 0.85 17 20 Karung
17 12 11310016 AYAM SIZE Z PR FROZ(*) PC 0.58 23.2 40 Karung
18 13 11310017 AYAM SIZE 0 PR FROZEN(*) PC 0.65 26 40 Karung
19 14 11310018 AYAM SIZE 1 PR FROZEN(*) PC 0.75 22.5 30 Karung
20 15 11310019 AYAM SIZE 2 PR FROZEN(*) PC 0.85 17 20 Karung
21 16 11310021 AYAM SIZE BESAR (B) FROZ (1-1.1)KG/PC(*) PC 1.05 21 20 Karung
22 17 11310022 AYAM SIZE A PR (0.9-1)KG/PC(*) PC 0.95 19 20 Karung
23 18 11310024 AYAM SIZE A FROZEN (0.9-1)KG/PC(*) PC 0.95 19 20 Karung
24 19 11310025 AYAM SIZE SUPER (C) FROZ(1.1-1.2)KG/ PC(*) PC 1.15 23 20 Karung
25 20 11310026 AYAM SIZE JUMBO (D) FROZ (1.2- 1.3)KG/PC(*) pc 1.25 20 16 Karung
26 21 11410043 PARTING 10 SIZE D FRESH BENSU 1.25 KG/PAC(*) PAC 1.25 12.5 10 Bag
27 22 11420055 PARTING 12 ALL SIZE FROZ/PAC(*) PAC 1 10 10 Box
28 23 11600053 BONELESS LEG FROZEN 1 KG(*) kg 1 10 10 Box
29 24 11620056 SBL (FILLET PAHA) 1 KG(*) kg 1 10 10 Box
30 25 11640053 PAHA UTUH (1 KG)(*) PAC 1 10 10 Box
31 26 11650053 PAHA ATAS 1 KG(*) kg 1 10 10 Box
32 27 11660050 PAHA BAWAH (1 KG)(*) PAC 1 10 10 Box
33 28 11690053 SBB (FILLET DADA )1 KG(*) kg 1 10 10 Box
34 29 11690081 SBB JUMBO FZ (2.0 - 2.2 KG/PAC)(*) PAC 2 20 10 Karung
35 30 11710051 DADA UTUH (1 KG)(*) kg 1 10 10 Box
36 31 11720055 FULL WING FROZ PACK 1 KG(*) kg 1 10 10 Box
37 32 11730050 MIDDLE WING FROZ PACK 1 KG(*) PAC 1 10 10 Box
38 33 11750050 FILLET MITRA 1 KG(*) KG 1 10 10 Box
39 34 11840002 AYAM JANTAN BKKL SZ 0 (600-700) GR/PC(*) PC 0 0 0 Karung
40 35 11959937 SATE AYAM FRESHMART 360 GR (PAC) PAC 0.36 7.2 20 Box
41 36 12010111 FIESTA CRISPY BUBBLE 400 GR/PAC PAC 0.4 4.8 12 Box
42 37 12010112 FIESTA CHICKEN NUGGET 400 GR/PAC PAC 0.4 4.8 12 Box
43 38 12010113 FIESTA CHICKEN NUGGET 200 GR/PAC PAC 0.2 4.8 24 Box
44 39 12010115 FIESTA NUGGET ZOO 400 GR/PAC PAC 0.4 4.8 12 Box
45 40 12010116 FIESTA NUGGET DINO 400 GR/PAC PAC 0.4 4.8 12 Box
46 41 12010117 FIESTA NUGGET HAPPY STAR 400 GR/PAC PAC 0.4 4.8 12 Box
47 42 12010119 FIESTA NUGGET CHEESE 123 400 GR/PAC PAC 0.4 4.8 12 Box
48 43 12010121 FIESTA NUGGET PIZZABC 400 GR/PAC PAC 0.4 4.8 12 Box
49 44 12010122 FIESTA CHEESY LOVER 400 GR/PAC PAC 0.4 4.8 12 Box
50 45 12010123 FIESTA GARLIC CHEESE 400 GR/PAC PAC 0.4 4.8 12 Box
51 46 12010124 FIESTA CHEESY CHIC W/BROCCOLI 400 GR/PAC PAC 0.4 4.8 12 Box
52 47 12010127 FIESTA SPICY NUGGET 400 GR/PAC PAC 0.4 4.8 12 Box
53 48 12010128 FIESTA VOLCANO CHEESE 400 GR/PAC PAC 0.4 4.8 12 Box
54 49 12010129 FIESTA CHEESY BOMBS CHICKEN NUGGET 400 GR PAC 0.4 4.8 12 Box
55 50 12010402 GOLDEN FIESTA NUGGET W/PINEAPPLE SAUCE 500 GR PAC 0.5 5 10 Box
56 51 12010509 CHAMP CRUNCHY NUGGET 450 GR/PAC PAC 0.45 4.5 10 Box
57 52 12010510 CHAMP NUGGET AYAM 225 GR/PAC PAC 0.225 4.95 22 Box
58 53 12010511 CHAMP NUGGET AYAM 450 GR/PAC PAC 0.45 5.4 12 Box
59 54 12010512 CHAMP NUGGET AYAM 900 GR/PAC PAC 0.9 5.4 6 Box
60 55 12010513 CHAMP NUGGET ABC KOMBINASI 225 GR/PAC PAC 0.225 4.95 22 Box
61 56 12010514 CHAMP NUGGET ABC KOMBINASI 450 GR/PAC PAC 0.45 5.4 12 Box
62 57 12010515 CHAMP KOIN KOMBINASI 450 GR/PAC PAC 0.45 5.4 12 Box
63 58 12010516 CHAMP KOIN KOMBINASI 200 GR/PAC PAC 0.2 6 30 Box
64 59 12010517 CHAMP NUGGET STICK 225 GR/PAC PAC 0.225 4.95 22 Box
65 60 12010518 CHAMP NUGGET STICK 450 GR/PAC PAC 0.45 5.4 12 Box
66 61 12010519 CHAMP NUGGET STICK 900 GR/PAC PAC 0.9 5.4 6 Box
67 62 12010520 CHAMP CHICKEN NUGGET BENTUK 123 450 GR/PAC PAC 0.45 5.4 12 Box
68 63 12010521 CHAMP NUGGET HOTZZ LEVEL 5 450 GR/PAC PAC 0.45 5.4 12 Box
69 64 12010606 CHAMP CRUNCHY NUGGET 225 GR/PAC PAC 0.225 5.625 25 Box
70 65 12010707 CHAMP MITRA NUGGET COIN 200 GR (NEW) PAC 0.2 6 30 Box
71 66 12010801 OKEY NUGGET 500GR PAC 0.5 5 10 Box
72 67 12012201 ASIMO NUGGET KOMBINASI 500 GR/PAC PAC 0.5 5 10 Box
73 68 12012202 ASIMO NUGGET KOMBINASI 1 KG/PAC PAC 1 5 5 Box
74 69 12012203 ASIMO NUGGET KOMBINASI 250 GR/PAC PAC 0.25 5 20 Box
75 70 12012501 AKUMO CHICKEN NAGET 250 GR PAC 0.25 5 20 Box
76 71 12012502 AKUMO CHICKEN NUGGET 500 GR PAC 0.5 5 10 Box
77 72 12012503 AKUMO CHICKEN NUGGET 1000 GR PAC 1 5 5 Box
78 73 12012504 AKUMO COIN 200 GR/PAC pack 0.2 6 30 Box
79 74 12012505 AKUMO KOIN 400 GR/PAC PAC 0.4 6 15 Box
80 75 12020102 FIESTA SPICY WING 400 GR/PAC PAC 0.4 4.8 12 Box
81 76 12020401 GOLDEN FIESTA SP WING 500 GR PAC 0.5 5 10 Box
82 77 12030101 FIESTA STIKIE 400 GR/PAC PAC 0.4 4.8 12 Box
83 78 12030102 FIESTA STIKIE 200 GR/PAC PAC 0.2 4.8 24 Box
84 79 12030403 GOLDEN FIESTA STIKIE W/ SWEET CHILLI SAUCE 500GR PAC 0.5 5 10 Box
85 80 12030801 OKEY STICK 1000 GR PAC 1 5 5 Box
86 81 12030802 OKEY STICK 500GR PAC 0.5 5 10 Box
87 82 12032201 ASIMO STICK KOMBINASI 500 GR/PAC PAC 0.5 5 10 Box
88 83 12032202 ASIMO STICK KOMBINASI 1000 GR/PAC PAC 1 5 5 Box
89 84 12032203 ASIMO STIK KOMBINASI 250 GR/PAC PAC 0.25 5 20 Box
90 85 12032501 AKUMO CHICKEN STICK 250 GR PAC 0.25 5 20 Box
91 86 12032502 AKUMO CHICKEN STIK 500 GR PAC 0.5 5 10 Box
92 87 12032503 AKUMO CHICKEN STICK 1000 GR PAC 1 5 5 Box
93 88 12040101 FIESTA SCHNITZEL 400 GR/PAC PAC 0.4 4.8 12 Box
94 89 12040102 FIESTA CRISPY BUBBLE KATSU 400 GR/PAC PAC 0.4 4.8 12 Box
95 90 12040404 GOLDEN FIESTA CORDON BLEU BBQ SAUCE 500 GR PAC 0.5 5 10 Box
96 91 12040406 GOLDEN FIESTA KATSU W/CHEESE SAUCE 500 GR/PAC PAC 0.5 5 10 Box
97 92 12050103 FIESTA FRIED CHICKEN 400 GR/PAC PAC 0.4 4.8 12 Box
98 93 12050104 FIESTA HOT & CRISPY FRIED CHICKEN 400 GR/PAC PAC 0.4 4.8 12 Box
99 94 12050401 GOLDEN FIESTA CRISPY WING W/SP GLAZ SC 500 GR/PAC PAC 0.5 5 10 Box
100 95 12060103 FIESTA KARAGE 200 GR/PAC PAC 0.2 4.8 24 Box
101 96 12060104 FIESTA KARAGE 400 GR/PAC PAC 0.4 4.8 12 Box
102 97 12060105 FIESTA SPICY KARAGE 400 GR/PAC PAC 0.4 4.8 12 Box
103 98 12060402 GOLDEN FIESTA KARAGE CHILI SAUCE 500GR PAC 0.5 5 10 Box
104 99 12070101 FIESTA POK-POK 400 GR/PAC (NEW) PAC 0.4 4.8 12 Box
105 100 12080101 FIESTA SPICY CHICK 400 GR/PAC PAC 0.4 4.8 12 Box
106 101 12130102 FIESTA CRISPY BURGER 360 GR (NEW) PAC 0.36 7.2 20 Box
107 102 12130504 CHAMP BURGER 315 GR (NEW) PAC 0.315 6.3 20 Box
108 103 12140105 FIESTA CHICK TOFU 400 GR/PAC PAC 0.4 4.8 12 Box
109 104 12150201 FIESTA DS CRISPY CRUNCH 300 GR/PAC PAC 0.3 3 10 Box
110 105 12150501 CHAMP CRUNCHY HOTZZ 300 GR/PAC PAC 0.3 3 10 Box
111 106 12190103 FIESTA DELISTRIPE 400 GR/PAC PAC 0.4 4.8 12 Box
112 107 12240102 FIESTA CHEESY ITALIAN R/BITES 400 GR/PAC PAC 0.4 4.8 12 Box
113 108 12240103 FIESTA YAKINIKU R/BITES 400 GR/PAC PAC 0.4 4.8 12 Box
114 109 13010101 FIESTA CHICK SSG 300 GR PAC 0.3 6 20 Box
115 110 13010102 FIESTA CHICK SSG 500 GR PAC 0.5 5 10 Box
116 111 13010103 FIESTA CHICK SSG 200 GR/PAC PAC 0.2 5 25 Box
117 112 13010111 FIESTA SOSIS BRATWURST 300 GR PAC 0.3 6 20 Box
118 113 13010112 FIESTA CHEESE SSG 300 GR PAC 0.3 6 20 Box
119 114 13010113 FIESTA SOSIS CURRYWURST 300 GR PAC 0.3 6 20 Box
120 115 13010114 FIESTA SSG BOCKWURST 300GR PAC 0.3 6 20 Box
121 116 13010115 FIESTA SSG WIENER 300GR PAC 0.3 6 20 Box
122 117 13010116 FIESTA SSG ORIGINAL 300 GR PAC 0.3 6 20 Box
123 118 13010117 FIESTA SSG FRANKFURTER 300GR PAC 0.3 6 20 Box
124 119 13010118 FIESTA RTG SSG 65 GR/PAC PAC 0.065 4.68 72 Box
125 120 13010119 FIESTA RTG C/SPICY KOREAN 60 GR/PAC PAC 0.06 4.32 72 Box
126 121 13010120 FIESTA RTG C/CHEESY MELTS 65 GR/PAC PAC 0.065 4.68 72 Box
127 122 13010122 FIESTA RTG SAUSAGE WITH HOT LAVA 60G PAC 0.06 4.32 72 Box
128 123 13010123 FIESTA RTG SAUSAGE WITH CHEESE LAVA 60G PAC 0.06 4.32 72 Box
129 124 13010124 FIESTA RTG SAUSAGE HICKORY SAUCE 60GR PAC 0.06 4.32 72 Box
130 125 13010125 FIESTA RTG SAUSAGE WITH MENTAI LAVA 60GR PAC 0.06 4.32 72 Box
131 126 13010510 CHAMP CHICK SSG 75 GR PAC 0.075 6 80 Box
132 127 13010513 CHAMP CHICK SSG 375 GR PAC 0.375 5.625 15 Box
133 128 13010514 CHAMP CHICK SSG 1000 GR PAC 1 10 10 Box
134 129 13010518 CHAMP SSG JUMBO BAKAR 500 GR/PAC PAC 0.5 5 10 Box
135 130 13010519 CHAMP SSG BAKAR MINI 500 GR/PAC-INACT PAC 0.5 5 10 Box
136 131 13010521 CHAMP CHICK SSG 150 GR/PAC (NEW) PAC 0.15 6.75 45 Box
137 132 13010523 CHAMP CHICK SSG AYAM MADU 300 GR/PAC PAC 0.3 6 20 Box
138 133 13010524 CHAMP SSG JUMBO BAKAR 500 GR/PAC (NEW) PAC 0.5 5 10 Box
139 134 13010525 CHAMP SSG BAKAR MINI 500 GR/PAC (NEW) PAC 0.5 5 10 Box
140 135 13010809 OKEY CHICK SSG 500GR-INACT PAC 0.5 10 20 Box
141 136 13010815 OKEY SSG BAKAR JUMBO 500 GR/PAC (NEW) PAC 0.5 5 10 Box
142 137 13010816 OKEY SSG BAKAR MINI 500 GR/PAC (NEW) PAC 0.5 5 10 Box
143 138 13010817 OKEY SSG CHICK KOMBINASI 500 GR (EXTRA1) PAC 0.5 10 20 Box
144 139 13010818 OKEY SSG CHICK KOMBINASI 1 KG (EXTR$A2) PAC 1 10 10 Box
145 140 13012205 ASIMO SOSIS AYAM KOMBINASI 375 GR (PAC) PAC 0.375 5.625 15 Box
146 141 13012206 ASIMO SOSIS AYAM KOMBINASI 500 GR PAC 0.5 10 20 Box
147 142 13012207 ASIMO SOSIS AYAM KOMBINASI 750 GR PAC 0.75 6 8 Box
148 143 13012208 ASIMO SOSIS AYAM KOMBINASI 1000 GR PAC 1 10 10 Box
149 144 13012209 ASIMO SSG CHICK KOMBINASI 500 GR (EXTRA1) PAC 0.5 10 20 Box
150 145 13012210 ASIMO SSG CHICK KOMBINASI 1 KG (EXTR$A2) PAC 1 10 10 Box
151 146 13030101 FIESTA CHICK MEAT BALL 300 GR PAC 0.3 6 20 Box
152 147 13030102 FIESTA CHICK MEATBALL 500 GR PAC 0.5 5 10 Box
153 148 13030501 CHAMP CHICK MEATBALL 200 GR PAC 0.2 5 25 Box
154 149 13030502 CHAMP CHICK MEATBALL 500 GR PAC 0.5 5 10 Box
155 150 13050101 FIESTA SCB 250 GR PAC 0.25 5 20 Box
156 151 13050105 FIESTA CHICKEN SLICE 300 GR PAC 0.3 6 20 Box
157 152 13050106 FIESTA BEEF SLICE 300 GR PAC 0.3 6 20 Box
158 153 13070501 CHAMP BEEF SSG SERBAGUNA 150 GR PAC 0.15 6.75 45 Box
159 154 13070502 CHAMP BEEF SSG SERBAGUNA 375GR PAC 0.375 9 24 Box
160 155 13070505 CHAMP BEEF SSG GORENG 375 GR PAC 0.375 5.625 15 Box
161 156 13070506 CHAMP FRANKFURTER SSG 375GR PAC 0.375 5.625 15 Box
162 157 13100512 CHAMP CHICK SSG S/SANTAP ORIG 546GR (CAN) can 0.546 3.276 6 Box
163 158 13110504 CHAMP BEEF BALL 500GR PAC 0.5 5 10 Box
164 159 13170510 CHAMP BEEF BBQ SSG S/SANTAP 546GR (CAN) can 0.546 3.276 6 Box
165 160 15010101 FIESTA SHOESTRING 500 GR PAC 0.5 6 12 Box
166 161 15010102 FIESTA SHOESTRING 1000 GR PAC 1 12 12 Box
167 162 15010107 FIESTA FRENCH F SHOESTRING INSTITUSI 2KG PAC 2 12 6 Box
168 163 15020101 FIESTA STRAIGHT CUT 500 GR PAC 0.5 6 12 Box
169 164 15020102 FIESTA STRAIGHT CUT 1000 GR PAC 1 12 12 Box
170 165 15030101 FIESTA CRINKLE CUT 500 GR PAC 0.5 6 12 Box
171 166 15030102 FIESTA CRINKLE CUT 1000 GR PAC 1 12 12 Box
172 167 15040101 FIESTA BATTER COATED 500 GR PAC 0.5 6 12 Box
173 168 15040102 FIESTA BATTER COATED 1000 GR PAC 1 12 12 Box
174 169 16060103 FIESTA CHICK SIOMAY 900GR PAC 0.9 10.8 12 Box
175 170 16060113 FIESTA CHICK SIOMAY 180GR (NEW) PAC 0.18 5.4 30 Box
176 171 16060114 FIESTA GYOZA 180 GR (NEW) PAC 0.18 5.4 30 Box
177 172 16060119 FIESTA RTG SIOMAY 54 GR/PAC PAC 0.054 3.888 72 Box
178 173 16060120 FIESTA KEECHO 400 GR/PAC PAC 0.4 4.8 12 Box
179 174 16060121 FIESTA CHICKEN TOFU 400 GR/PAC (NEW) PAC 0.4 4.8 12 Box
180 175 16060503 CHAMP CHICK&FISH SIOMAY 180 GR (NEW) PAC 0.18 5.4 30 Box
181 176 17200109 FIESTA RTS C/TERIYAKI 300GR/PAC PAC 0.3 2.4 8 Box
182 177 17200110 FIESTA RTS C/RENDANG 300GR/PAC PAC 0.3 2.4 8 Box
183 178 17200111 FIESTA RTS C/W RUJAK SC 300GR/PAC PAC 0.3 2.4 8 Box
184 179 17200112 FIESTA RTS C/W S/MATAH 300GR/PAC PAC 0.3 2.4 8 Box
185 180 17210106 FIESTA RTS B/YAKINIKU 300GR/PAC PAC 0.3 2.4 8 Box
186 181 17210107 FIESTA RTS B/RENDANG 300GR/PAC PAC 0.3 2.4 8 Box
187 182 17210108 FIESTA RTS B/BLACKPEPPER 300GR/PAC PAC 0.3 2.4 8 Box
188 183 17210109 FIESTA RTS B/BULGOGI 300GR/PAC PAC 0.3 2.4 8 Box
189 184 18050102 FIESTA RTG BAKSO KEJU 60 GR/PAC PAC 0.06 4.32 72 Box
190 185 18050103 FIESTA RTG BAKSO BAKAR BBQ 60 GR/PAC PAC 0.06 4.32 72 Box
191 186 18050104 FIESTA RTG BEEF BALL WITH MENTAI LAVA 55GR PAC 0.055 3.96 72 Box
192 187 18050105 FIESTA RTG BEEF BALL WITH CHEESE LAVA 55GR PAC 0.055 3.96 72 Box
193 188 20010101 FIESTA CRISPY CRUMBS 200 GR PAC 0.2 4.8 24 Box
194 189 20010102 FIESTA TP ROTI PUTIH 200 GR PAC 0.2 4.8 24 Box
195 190 20040101 FIESTA RAMEN BEKU 570 GR/PAC PAC 0.57 6.84 12 Box
196 191 20120102 FIESTA T/B AYAM GORENG 80 GR PAC 0.08 3.2 40 Box
197 192 20120105 FIESTA T/B SERBAGUNA  80 GR PAC 0.08 3.2 40 Box
198 193 20120106 FIESTA T/B KREMES 80 GR PAC 0.08 3.2 40 Box
199 194 20120115 FIESTA RACIK AYAM GORENG 20 GR/PAC PAC 0.02 1.6 80 Box
200 195 20120116 FIESTA RACIK NASI GORENG 20 GR/PAC PAC 0.02 1.6 80 Box
201 196 21000123 FIESTA RICE W/GEPREK CHICKEN 320GR/PAC PAC 0.32 1.92 6 Box
202 197 21000124 FIESTA RICE W/CHICK RUJAK 320 GR/PAC PAC 0.32 1.92 6 Box
203 198 21000125 FIESTA RICE W/KOREAN BBQ CHICK 320 GR/PAC PAC 0.32 1.92 6 Box
204 199 21000126 NEW FIESTA CHICK RENDANG W RICE 320GR (PAC) PAC 0.32 1.92 6 Box
205 200 21000127 NEW FIESTA CHICK TERIYAKI W RICE 320GR (PAC) PAC 0.32 1.92 6 Box
206 201 21000128 NEW FIESTA CHICK TANDORI W RICE 320GR (PAC) PAC 0.32 1.92 6 Box
207 202 21000129 NEW FIESTA RICE W KARAGE&SSS 320GR (PAC) PAC 0.32 1.92 6 Box
208 203 21000130 NEW FIESTA RICE W/C CHEESE BULDAK 320GR (PAC) PAC 0.32 1.92 6 Box
209 204 21000131 NEW FIESTA RICE W CHIC CURRY 320GR (PAC) PAC 0.32 1.92 6 Box
210 205 21000132 NEW FIESTA RICE W CHICK DONBURI 320GR (PAC) PAC 0.32 1.92 6 Box
211 206 21000133 NEW FIESTA RICE W CHICK SATAY 320GR (PAC) PAC 0.32 1.92 6 Box
212 207 21000134 NEW FIESTA COCONUT RICE W SPICY CHICK 320GR (PAC) PAC 0.32 1.92 6 Box
213 208 21000135 NEW FIESTA RICE W POPBITES S/MATAH 320GR (PAC) PAC 0.32 1.92 6 Box
214 209 21000136 NEW FIESTA TUMERIC W POPBITES 320GR (PAC) PAC 0.32 1.92 6 Box
215 210 21000137 FIESTA HAINAMESE CHICKEN RICE 320GR (PAC) PAC 0.32 1.92 6 Box
216 211 21010101 FIESTA TRUFFLE GYUDON 320 GR/PAC PAC 0.32 1.92 6 Box
217 212 21010102 NEW FIESTA BEEF YAKINIKU W RICE 320GR (PAC) PAC 0.32 1.92 6 Box
218 213 21010103 NEW FIESTA BEEF BULGOGI W RICE 320GR (PAC) PAC 0.32 1.92 6 Box
219 214 21010104 NEW FIESTA BEEF RENDANG W RICE 320GR (PAC) PAC 0.32 1.92 6 Box
220 215 21010105 NEW FIESTA RICE W BEEF BLACKPEPPER 320GR (PAC) PAC 0.32 1.92 6 Box
221 216 21200107 NEW FIESTA SPAGHETTI CARBONARA 300GR (PAC) PAC 0.3 1.8 6 Box
222 217 21200108 NEW FIESTA SPAGHETTI CHIC BOLOGNESE 320GR (PAC) PAC 0.32 1.92 6 Box
223 218 21200109 NEW FIESTA ITALIAN MEATBALL SPAGHETTI 320GR (PAC) PAC 0.32 1.92 6 Box
224 219 21310103 NEW FIESTA SCB&S/SSG FRIED RICE 320GR (PAC) PAC 0.32 1.92 6 Box
225 220 21500101 FIESTA CHICK SSG & C. BALL PIZZA 230GR/PAC PAC 0.23 1.15 5 Box
226 221 21500102 FIESTA CHEESY BEEF PIZZA 230GR/PAC PAC 0.23 1.15 5 Box
227 222 1188002W PAHA ATAS 25-30 G FZ (*) PAC 1 10 10 Box
228 223 1195008A RTC CHICKEN KALASAN 400 GR (PAC) PAC 0.41 4.1 10 Box
229 224 1195008E RTC CHICKEN TERIYAKI 400 GR (PAC) PAC 0.4 3.2 8 Box
230 225 1195008X RTC CHICKEN SPICY 400 GR (PAC) PAC 0.4 3.2 8 Box
-233
View File
@@ -1,233 +0,0 @@
sku,nama_barang
11048006,BEBEK PARTING-NEW(*)
11110059,CEKER BERKUKU FROZEN PACK 1 KG(*)
11110074,CEKER 1 KG FROZEN (20 PAC/KARUNG)(*)
11140051,AMPELA FROZEN PACK 1 KG(*)
11140062,AMPELA 1 KG FROZEN (20 PAC/KARUNG)(*)
11148002,AMPELA BEBEK FROZEN 1 KG/PACK (NEW)(*)
11150052,HATI FROZEN PACK 1 KG(*)
11150055,JANTUNG FROZEN PACK 1 KG(*)
11150064,HATI 1 KG FROZEN (20 PAC/KARUNG)(*)
11150065,JANTUNG 1 KG FROZEN (20 PAC/KARUNG)(*)
11310012,AYAM SIZE 0 (0.6-0.7)KG(*)
11310013,AYAM SIZE1 FROZEN (0.75-0.8) KG(*)
11310014,AYAM SIZE 2 FROZEN (0.8-0.9)KG(*)
11310016,AYAM SIZE Z PR FROZ(*)
11310017,AYAM SIZE 0 PR FROZEN(*)
11310018,AYAM SIZE 1 PR FROZEN(*)
11310019,AYAM SIZE 2 PR FROZEN(*)
11310021,AYAM SIZE BESAR (B) FROZ (1-1.1)KG/PC(*)
11310022,AYAM SIZE A PR (0.9-1)KG/PC(*)
11310024,AYAM SIZE A FROZEN (0.9-1)KG/PC(*)
11310025,AYAM SIZE SUPER (C) FROZ(1.1-1.2)KG/ PC(*)
11310026,AYAM SIZE JUMBO (D) FROZ (1.2- 1.3)KG/PC(*)
11318301,BEBEK MUDA-BD1(1.0-1.1 KG)-NEW(*)
11318306,CP DUCK PEKING 1.5-1.6 KG/PC(*)
11318308,BEBEK PEKING SPR BD5(1.7 -1.8 )Kg-NEW(*)
11410043,PARTING 10 SIZE D FRESH BENSU 1.25 KG/PAC(*)
11420055,PARTING 12 ALL SIZE FROZ/PAC(*)
11600053,BONELESS LEG FROZEN 1 KG(*)
11620056,SBL (FILLET PAHA) 1 KG(*)
11640053,PAHA UTUH (1 KG)(*)
11650053,PAHA ATAS 1 KG(*)
11660050,PAHA BAWAH (1 KG)(*)
11690053,SBB (FILLET DADA )1 KG(*)
11690081,SBB JUMBO FZ (2.0 - 2.2 KG/PAC)(*)
11710051,DADA UTUH (1 KG)(*)
11720055,FULL WING FROZ PACK 1 KG(*)
11730050,MIDDLE WING FROZ PACK 1 KG(*)
11750050,FILLET MITRA 1 KG(*)
11818300,CP-BEBEK GORENG 400GR/PAC
11840002,AYAM JANTAN BKKL SZ 0 (600-700) GR/PC(*)
11959937,SATE AYAM FRESHMART 360 GR (PAC)
12010111,FIESTA CRISPY BUBBLE 400 GR/PAC
12010112,FIESTA CHICKEN NUGGET 400 GR/PAC
12010113,FIESTA CHICKEN NUGGET 200 GR/PAC
12010115,FIESTA NUGGET ZOO 400 GR/PAC
12010116,FIESTA NUGGET DINO 400 GR/PAC
12010117,FIESTA NUGGET HAPPY STAR 400 GR/PAC
12010119,FIESTA NUGGET CHEESE 123 400 GR/PAC
12010121,FIESTA NUGGET PIZZABC 400 GR/PAC
12010122,FIESTA CHEESY LOVER 400 GR/PAC
12010123,FIESTA GARLIC CHEESE 400 GR/PAC
12010124,FIESTA CHEESY CHIC W/BROCCOLI 400 GR/PAC
12010127,FIESTA SPICY NUGGET 400 GR/PAC
12010128,FIESTA VOLCANO CHEESE 400 GR/PAC
12010129,FIESTA CHEESY BOMBS CHICKEN NUGGET 400 GR
12010402,GOLDEN FIESTA NUGGET W/PINEAPPLE SAUCE 500 GR
12010509,CHAMP CRUNCHY NUGGET 450 GR/PAC
12010510,CHAMP NUGGET AYAM 225 GR/PAC
12010511,CHAMP NUGGET AYAM 450 GR/PAC
12010512,CHAMP NUGGET AYAM 900 GR/PAC
12010513,CHAMP NUGGET ABC KOMBINASI 225 GR/PAC
12010514,CHAMP NUGGET ABC KOMBINASI 450 GR/PAC
12010515,CHAMP KOIN KOMBINASI 450 GR/PAC
12010516,CHAMP KOIN KOMBINASI 200 GR/PAC
12010517,CHAMP NUGGET STICK 225 GR/PAC
12010518,CHAMP NUGGET STICK 450 GR/PAC
12010519,CHAMP NUGGET STICK 900 GR/PAC
12010520,CHAMP CHICKEN NUGGET BENTUK 123 450 GR/PAC
12010521,CHAMP NUGGET HOTZZ LEVEL 5 450 GR/PAC
12010606,CHAMP CRUNCHY NUGGET 225 GR/PAC
12010707,CHAMP MITRA NUGGET COIN 200 GR (NEW)
12010801,OKEY NUGGET 500GR
12012201,ASIMO NUGGET KOMBINASI 500 GR/PAC
12012202,ASIMO NUGGET KOMBINASI 1 KG/PAC
12012203,ASIMO NUGGET KOMBINASI 250 GR/PAC
12012501,AKUMO CHICKEN NAGET 250 GR
12012502,AKUMO CHICKEN NUGGET 500 GR
12012503,AKUMO CHICKEN NUGGET 1000 GR
12012504,AKUMO COIN 200 GR/PAC
12012505,AKUMO KOIN 400 GR/PAC
12020102,FIESTA SPICY WING 400 GR/PAC
12020401,GOLDEN FIESTA SP WING 500 GR
12030101,FIESTA STIKIE 400 GR/PAC
12030102,FIESTA STIKIE 200 GR/PAC
12030403,GOLDEN FIESTA STIKIE W/ SWEET CHILLI SAUCE 500GR
12030801,OKEY STICK 1000 GR
12030802,OKEY STICK 500GR
12032201,ASIMO STICK KOMBINASI 500 GR/PAC
12032202,ASIMO STICK KOMBINASI 1000 GR/PAC
12032203,ASIMO STIK KOMBINASI 250 GR/PAC
12032501,AKUMO CHICKEN STICK 250 GR
12032502,AKUMO CHICKEN STIK 500 GR
12032503,AKUMO CHICKEN STICK 1000 GR
12040101,FIESTA SCHNITZEL 400 GR/PAC
12040102,FIESTA CRISPY BUBBLE KATSU 400 GR/PAC
12040404,GOLDEN FIESTA CORDON BLEU BBQ SAUCE 500 GR
12040406,GOLDEN FIESTA KATSU W/CHEESE SAUCE 500 GR/PAC
12050103,FIESTA FRIED CHICKEN 400 GR/PAC
12050104,FIESTA HOT & CRISPY FRIED CHICKEN 400 GR/PAC
12050401,GOLDEN FIESTA CRISPY WING W/SP GLAZ SC 500 GR/PAC
12060103,FIESTA KARAGE 200 GR/PAC
12060104,FIESTA KARAGE 400 GR/PAC
12060105,FIESTA SPICY KARAGE 400 GR/PAC
12060402,GOLDEN FIESTA KARAGE CHILI SAUCE 500GR
12070101,FIESTA POK-POK 400 GR/PAC (NEW)
12080101,FIESTA SPICY CHICK 400 GR/PAC
12130102,FIESTA CRISPY BURGER 360 GR (NEW)
12130504,CHAMP BURGER 315 GR (NEW)
12140105,FIESTA CHICK TOFU 400 GR/PAC
12150201,FIESTA DS CRISPY CRUNCH 300 GR/PAC
12150501,CHAMP CRUNCHY HOTZZ 300 GR/PAC
12190103,FIESTA DELISTRIPE 400 GR/PAC
12240102,FIESTA CHEESY ITALIAN R/BITES 400 GR/PAC
12240103,FIESTA YAKINIKU R/BITES 400 GR/PAC
13010101,FIESTA CHICK SSG 300 GR
13010102,FIESTA CHICK SSG 500 GR
13010103,FIESTA CHICK SSG 200 GR/PAC
13010111,FIESTA SOSIS BRATWURST 300 GR
13010112,FIESTA CHEESE SSG 300 GR
13010113,FIESTA SOSIS CURRYWURST 300 GR
13010114,FIESTA SSG BOCKWURST 300GR
13010115,FIESTA SSG WIENER 300GR
13010116,FIESTA SSG ORIGINAL 300 GR
13010117,FIESTA SSG FRANKFURTER 300GR
13010118,FIESTA RTG SSG 65 GR/PAC
13010119,FIESTA RTG C/SPICY KOREAN 60 GR/PAC
13010120,FIESTA RTG C/CHEESY MELTS 65 GR/PAC
13010122,FIESTA RTG SAUSAGE WITH HOT LAVA 60G
13010123,FIESTA RTG SAUSAGE WITH CHEESE LAVA 60G
13010124,FIESTA RTG SAUSAGE HICKORY SAUCE 60GR
13010125,FIESTA RTG SAUSAGE WITH MENTAI LAVA 60GR
13010510,CHAMP CHICK SSG 75 GR
13010513,CHAMP CHICK SSG 375 GR
13010514,CHAMP CHICK SSG 1000 GR
13010518,CHAMP SSG JUMBO BAKAR 500 GR/PAC
13010519,CHAMP SSG BAKAR MINI 500 GR/PAC-INACT
13010521,CHAMP CHICK SSG 150 GR/PAC (NEW)
13010523,CHAMP CHICK SSG AYAM MADU 300 GR/PAC
13010524,CHAMP SSG JUMBO BAKAR 500 GR/PAC (NEW)
13010525,CHAMP SSG BAKAR MINI 500 GR/PAC (NEW)
13010809,OKEY CHICK SSG 500GR-INACT
13010815,OKEY SSG BAKAR JUMBO 500 GR/PAC (NEW)
13010816,OKEY SSG BAKAR MINI 500 GR/PAC (NEW)
13010817,OKEY SSG CHICK KOMBINASI 500 GR (EXTRA1)
13010818,OKEY SSG CHICK KOMBINASI 1 KG (EXTR$A2)
13012205,ASIMO SOSIS AYAM KOMBINASI 375 GR (PAC)
13012206,ASIMO SOSIS AYAM KOMBINASI 500 GR
13012207,ASIMO SOSIS AYAM KOMBINASI 750 GR
13012208,ASIMO SOSIS AYAM KOMBINASI 1000 GR
13012209,ASIMO SSG CHICK KOMBINASI 500 GR (EXTRA1)
13012210,ASIMO SSG CHICK KOMBINASI 1 KG (EXTR$A2)
13030101,FIESTA CHICK MEAT BALL 300 GR
13030102,FIESTA CHICK MEATBALL 500 GR
13030501,CHAMP CHICK MEATBALL 200 GR
13030502,CHAMP CHICK MEATBALL 500 GR
13050101,FIESTA SCB 250 GR
13050105,FIESTA CHICKEN SLICE 300 GR
13050106,FIESTA BEEF SLICE 300 GR
13070501,CHAMP BEEF SSG SERBAGUNA 150 GR
13070502,CHAMP BEEF SSG SERBAGUNA 375GR
13070505,CHAMP BEEF SSG GORENG 375 GR
13070506,CHAMP FRANKFURTER SSG 375GR
13100512,CHAMP CHICK SSG S/SANTAP ORIG 546GR (CAN)
13110504,CHAMP BEEF BALL 500GR
13170510,CHAMP BEEF BBQ SSG S/SANTAP 546GR (CAN)
15010101,FIESTA SHOESTRING 500 GR
15010102,FIESTA SHOESTRING 1000 GR
15010107,FIESTA FRENCH F SHOESTRING INSTITUSI 2KG
15020101,FIESTA STRAIGHT CUT 500 GR
15020102,FIESTA STRAIGHT CUT 1000 GR
15030101,FIESTA CRINKLE CUT 500 GR
15030102,FIESTA CRINKLE CUT 1000 GR
15040101,FIESTA BATTER COATED 500 GR
15040102,FIESTA BATTER COATED 1000 GR
16060103,FIESTA CHICK SIOMAY 900GR
16060113,FIESTA CHICK SIOMAY 180GR (NEW)
16060114,FIESTA GYOZA 180 GR (NEW)
16060119,FIESTA RTG SIOMAY 54 GR/PAC
16060120,FIESTA KEECHO 400 GR/PAC
16060121,FIESTA CHICKEN TOFU 400 GR/PAC (NEW)
16060503,CHAMP CHICK&FISH SIOMAY 180 GR (NEW)
17200109,FIESTA RTS C/TERIYAKI 300GR/PAC
17200110,FIESTA RTS C/RENDANG 300GR/PAC
17200111,FIESTA RTS C/W RUJAK SC 300GR/PAC
17200112,FIESTA RTS C/W S/MATAH 300GR/PAC
17210106,FIESTA RTS B/YAKINIKU 300GR/PAC
17210107,FIESTA RTS B/RENDANG 300GR/PAC
17210108,FIESTA RTS B/BLACKPEPPER 300GR/PAC
17210109,FIESTA RTS B/BULGOGI 300GR/PAC
18050102,FIESTA RTG BAKSO KEJU 60 GR/PAC
18050103,FIESTA RTG BAKSO BAKAR BBQ 60 GR/PAC
18050104,FIESTA RTG BEEF BALL WITH MENTAI LAVA 55GR
18050105,FIESTA RTG BEEF BALL WITH CHEESE LAVA 55GR
20010101,FIESTA CRISPY CRUMBS 200 GR
20010102,FIESTA TP ROTI PUTIH 200 GR
20040101,FIESTA RAMEN BEKU 570 GR/PAC
20120102,FIESTA T/B AYAM GORENG 80 GR
20120105,FIESTA T/B SERBAGUNA  80 GR
20120106,FIESTA T/B KREMES 80 GR
20120115,FIESTA RACIK AYAM GORENG 20 GR/PAC
20120116,FIESTA RACIK NASI GORENG 20 GR/PAC
21000123,FIESTA RICE W/GEPREK CHICKEN 320GR/PAC
21000124,FIESTA RICE W/CHICK RUJAK 320 GR/PAC
21000125,FIESTA RICE W/KOREAN BBQ CHICK 320 GR/PAC
21000126,NEW FIESTA CHICK RENDANG W RICE 320GR (PAC)
21000127,NEW FIESTA CHICK TERIYAKI W RICE 320GR (PAC)
21000128,NEW FIESTA CHICK TANDORI W RICE 320GR (PAC)
21000129,NEW FIESTA RICE W KARAGE&SSS 320GR (PAC)
21000130,NEW FIESTA RICE W/C CHEESE BULDAK 320GR (PAC)
21000131,NEW FIESTA RICE W CHIC CURRY 320GR (PAC)
21000132,NEW FIESTA RICE W CHICK DONBURI 320GR (PAC)
21000133,NEW FIESTA RICE W CHICK SATAY 320GR (PAC)
21000134,NEW FIESTA COCONUT RICE W SPICY CHICK 320GR (PAC)
21000135,NEW FIESTA RICE W POPBITES S/MATAH 320GR (PAC)
21000136,NEW FIESTA TUMERIC W POPBITES 320GR (PAC)
21000137,FIESTA HAINAMESE CHICKEN RICE 320GR (PAC)
21010101,FIESTA TRUFFLE GYUDON 320 GR/PAC
21010102,NEW FIESTA BEEF YAKINIKU W RICE 320GR (PAC)
21010103,NEW FIESTA BEEF BULGOGI W RICE 320GR (PAC)
21010104,NEW FIESTA BEEF RENDANG W RICE 320GR (PAC)
21010105,NEW FIESTA RICE W BEEF BLACKPEPPER 320GR (PAC)
21200107,NEW FIESTA SPAGHETTI CARBONARA 300GR (PAC)
21200108,NEW FIESTA SPAGHETTI CHIC BOLOGNESE 320GR (PAC)
21200109,NEW FIESTA ITALIAN MEATBALL SPAGHETTI 320GR (PAC)
21310103,NEW FIESTA SCB&S/SSG FRIED RICE 320GR (PAC)
21500101,FIESTA CHICK SSG & C. BALL PIZZA 230GR/PAC
21500102,FIESTA CHEESY BEEF PIZZA 230GR/PAC
91000012,PHOTOCARD RTG
1188002W,PAHA ATAS 25-30 G FZ (*)
1195008A,RTC CHICKEN KALASAN 400 GR (PAC)
1195008E,RTC CHICKEN TERIYAKI 400 GR (PAC)
1195008X,RTC CHICKEN SPICY 400 GR (PAC)
1 sku nama_barang
2 11048006 BEBEK PARTING-NEW(*)
3 11110059 CEKER BERKUKU FROZEN PACK 1 KG(*)
4 11110074 CEKER 1 KG FROZEN (20 PAC/KARUNG)(*)
5 11140051 AMPELA FROZEN PACK 1 KG(*)
6 11140062 AMPELA 1 KG FROZEN (20 PAC/KARUNG)(*)
7 11148002 AMPELA BEBEK FROZEN 1 KG/PACK (NEW)(*)
8 11150052 HATI FROZEN PACK 1 KG(*)
9 11150055 JANTUNG FROZEN PACK 1 KG(*)
10 11150064 HATI 1 KG FROZEN (20 PAC/KARUNG)(*)
11 11150065 JANTUNG 1 KG FROZEN (20 PAC/KARUNG)(*)
12 11310012 AYAM SIZE 0 (0.6-0.7)KG(*)
13 11310013 AYAM SIZE1 FROZEN (0.75-0.8) KG(*)
14 11310014 AYAM SIZE 2 FROZEN (0.8-0.9)KG(*)
15 11310016 AYAM SIZE Z PR FROZ(*)
16 11310017 AYAM SIZE 0 PR FROZEN(*)
17 11310018 AYAM SIZE 1 PR FROZEN(*)
18 11310019 AYAM SIZE 2 PR FROZEN(*)
19 11310021 AYAM SIZE BESAR (B) FROZ (1-1.1)KG/PC(*)
20 11310022 AYAM SIZE A PR (0.9-1)KG/PC(*)
21 11310024 AYAM SIZE A FROZEN (0.9-1)KG/PC(*)
22 11310025 AYAM SIZE SUPER (C) FROZ(1.1-1.2)KG/ PC(*)
23 11310026 AYAM SIZE JUMBO (D) FROZ (1.2- 1.3)KG/PC(*)
24 11318301 BEBEK MUDA-BD1(1.0-1.1 KG)-NEW(*)
25 11318306 CP DUCK PEKING 1.5-1.6 KG/PC(*)
26 11318308 BEBEK PEKING SPR BD5(1.7 -1.8 )Kg-NEW(*)
27 11410043 PARTING 10 SIZE D FRESH BENSU 1.25 KG/PAC(*)
28 11420055 PARTING 12 ALL SIZE FROZ/PAC(*)
29 11600053 BONELESS LEG FROZEN 1 KG(*)
30 11620056 SBL (FILLET PAHA) 1 KG(*)
31 11640053 PAHA UTUH (1 KG)(*)
32 11650053 PAHA ATAS 1 KG(*)
33 11660050 PAHA BAWAH (1 KG)(*)
34 11690053 SBB (FILLET DADA )1 KG(*)
35 11690081 SBB JUMBO FZ (2.0 - 2.2 KG/PAC)(*)
36 11710051 DADA UTUH (1 KG)(*)
37 11720055 FULL WING FROZ PACK 1 KG(*)
38 11730050 MIDDLE WING FROZ PACK 1 KG(*)
39 11750050 FILLET MITRA 1 KG(*)
40 11818300 CP-BEBEK GORENG 400GR/PAC
41 11840002 AYAM JANTAN BKKL SZ 0 (600-700) GR/PC(*)
42 11959937 SATE AYAM FRESHMART 360 GR (PAC)
43 12010111 FIESTA CRISPY BUBBLE 400 GR/PAC
44 12010112 FIESTA CHICKEN NUGGET 400 GR/PAC
45 12010113 FIESTA CHICKEN NUGGET 200 GR/PAC
46 12010115 FIESTA NUGGET ZOO 400 GR/PAC
47 12010116 FIESTA NUGGET DINO 400 GR/PAC
48 12010117 FIESTA NUGGET HAPPY STAR 400 GR/PAC
49 12010119 FIESTA NUGGET CHEESE 123 400 GR/PAC
50 12010121 FIESTA NUGGET PIZZABC 400 GR/PAC
51 12010122 FIESTA CHEESY LOVER 400 GR/PAC
52 12010123 FIESTA GARLIC CHEESE 400 GR/PAC
53 12010124 FIESTA CHEESY CHIC W/BROCCOLI 400 GR/PAC
54 12010127 FIESTA SPICY NUGGET 400 GR/PAC
55 12010128 FIESTA VOLCANO CHEESE 400 GR/PAC
56 12010129 FIESTA CHEESY BOMBS CHICKEN NUGGET 400 GR
57 12010402 GOLDEN FIESTA NUGGET W/PINEAPPLE SAUCE 500 GR
58 12010509 CHAMP CRUNCHY NUGGET 450 GR/PAC
59 12010510 CHAMP NUGGET AYAM 225 GR/PAC
60 12010511 CHAMP NUGGET AYAM 450 GR/PAC
61 12010512 CHAMP NUGGET AYAM 900 GR/PAC
62 12010513 CHAMP NUGGET ABC KOMBINASI 225 GR/PAC
63 12010514 CHAMP NUGGET ABC KOMBINASI 450 GR/PAC
64 12010515 CHAMP KOIN KOMBINASI 450 GR/PAC
65 12010516 CHAMP KOIN KOMBINASI 200 GR/PAC
66 12010517 CHAMP NUGGET STICK 225 GR/PAC
67 12010518 CHAMP NUGGET STICK 450 GR/PAC
68 12010519 CHAMP NUGGET STICK 900 GR/PAC
69 12010520 CHAMP CHICKEN NUGGET BENTUK 123 450 GR/PAC
70 12010521 CHAMP NUGGET HOTZZ LEVEL 5 450 GR/PAC
71 12010606 CHAMP CRUNCHY NUGGET 225 GR/PAC
72 12010707 CHAMP MITRA NUGGET COIN 200 GR (NEW)
73 12010801 OKEY NUGGET 500GR
74 12012201 ASIMO NUGGET KOMBINASI 500 GR/PAC
75 12012202 ASIMO NUGGET KOMBINASI 1 KG/PAC
76 12012203 ASIMO NUGGET KOMBINASI 250 GR/PAC
77 12012501 AKUMO CHICKEN NAGET 250 GR
78 12012502 AKUMO CHICKEN NUGGET 500 GR
79 12012503 AKUMO CHICKEN NUGGET 1000 GR
80 12012504 AKUMO COIN 200 GR/PAC
81 12012505 AKUMO KOIN 400 GR/PAC
82 12020102 FIESTA SPICY WING 400 GR/PAC
83 12020401 GOLDEN FIESTA SP WING 500 GR
84 12030101 FIESTA STIKIE 400 GR/PAC
85 12030102 FIESTA STIKIE 200 GR/PAC
86 12030403 GOLDEN FIESTA STIKIE W/ SWEET CHILLI SAUCE 500GR
87 12030801 OKEY STICK 1000 GR
88 12030802 OKEY STICK 500GR
89 12032201 ASIMO STICK KOMBINASI 500 GR/PAC
90 12032202 ASIMO STICK KOMBINASI 1000 GR/PAC
91 12032203 ASIMO STIK KOMBINASI 250 GR/PAC
92 12032501 AKUMO CHICKEN STICK 250 GR
93 12032502 AKUMO CHICKEN STIK 500 GR
94 12032503 AKUMO CHICKEN STICK 1000 GR
95 12040101 FIESTA SCHNITZEL 400 GR/PAC
96 12040102 FIESTA CRISPY BUBBLE KATSU 400 GR/PAC
97 12040404 GOLDEN FIESTA CORDON BLEU BBQ SAUCE 500 GR
98 12040406 GOLDEN FIESTA KATSU W/CHEESE SAUCE 500 GR/PAC
99 12050103 FIESTA FRIED CHICKEN 400 GR/PAC
100 12050104 FIESTA HOT & CRISPY FRIED CHICKEN 400 GR/PAC
101 12050401 GOLDEN FIESTA CRISPY WING W/SP GLAZ SC 500 GR/PAC
102 12060103 FIESTA KARAGE 200 GR/PAC
103 12060104 FIESTA KARAGE 400 GR/PAC
104 12060105 FIESTA SPICY KARAGE 400 GR/PAC
105 12060402 GOLDEN FIESTA KARAGE CHILI SAUCE 500GR
106 12070101 FIESTA POK-POK 400 GR/PAC (NEW)
107 12080101 FIESTA SPICY CHICK 400 GR/PAC
108 12130102 FIESTA CRISPY BURGER 360 GR (NEW)
109 12130504 CHAMP BURGER 315 GR (NEW)
110 12140105 FIESTA CHICK TOFU 400 GR/PAC
111 12150201 FIESTA DS CRISPY CRUNCH 300 GR/PAC
112 12150501 CHAMP CRUNCHY HOTZZ 300 GR/PAC
113 12190103 FIESTA DELISTRIPE 400 GR/PAC
114 12240102 FIESTA CHEESY ITALIAN R/BITES 400 GR/PAC
115 12240103 FIESTA YAKINIKU R/BITES 400 GR/PAC
116 13010101 FIESTA CHICK SSG 300 GR
117 13010102 FIESTA CHICK SSG 500 GR
118 13010103 FIESTA CHICK SSG 200 GR/PAC
119 13010111 FIESTA SOSIS BRATWURST 300 GR
120 13010112 FIESTA CHEESE SSG 300 GR
121 13010113 FIESTA SOSIS CURRYWURST 300 GR
122 13010114 FIESTA SSG BOCKWURST 300GR
123 13010115 FIESTA SSG WIENER 300GR
124 13010116 FIESTA SSG ORIGINAL 300 GR
125 13010117 FIESTA SSG FRANKFURTER 300GR
126 13010118 FIESTA RTG SSG 65 GR/PAC
127 13010119 FIESTA RTG C/SPICY KOREAN 60 GR/PAC
128 13010120 FIESTA RTG C/CHEESY MELTS 65 GR/PAC
129 13010122 FIESTA RTG SAUSAGE WITH HOT LAVA 60G
130 13010123 FIESTA RTG SAUSAGE WITH CHEESE LAVA 60G
131 13010124 FIESTA RTG SAUSAGE HICKORY SAUCE 60GR
132 13010125 FIESTA RTG SAUSAGE WITH MENTAI LAVA 60GR
133 13010510 CHAMP CHICK SSG 75 GR
134 13010513 CHAMP CHICK SSG 375 GR
135 13010514 CHAMP CHICK SSG 1000 GR
136 13010518 CHAMP SSG JUMBO BAKAR 500 GR/PAC
137 13010519 CHAMP SSG BAKAR MINI 500 GR/PAC-INACT
138 13010521 CHAMP CHICK SSG 150 GR/PAC (NEW)
139 13010523 CHAMP CHICK SSG AYAM MADU 300 GR/PAC
140 13010524 CHAMP SSG JUMBO BAKAR 500 GR/PAC (NEW)
141 13010525 CHAMP SSG BAKAR MINI 500 GR/PAC (NEW)
142 13010809 OKEY CHICK SSG 500GR-INACT
143 13010815 OKEY SSG BAKAR JUMBO 500 GR/PAC (NEW)
144 13010816 OKEY SSG BAKAR MINI 500 GR/PAC (NEW)
145 13010817 OKEY SSG CHICK KOMBINASI 500 GR (EXTRA1)
146 13010818 OKEY SSG CHICK KOMBINASI 1 KG (EXTR$A2)
147 13012205 ASIMO SOSIS AYAM KOMBINASI 375 GR (PAC)
148 13012206 ASIMO SOSIS AYAM KOMBINASI 500 GR
149 13012207 ASIMO SOSIS AYAM KOMBINASI 750 GR
150 13012208 ASIMO SOSIS AYAM KOMBINASI 1000 GR
151 13012209 ASIMO SSG CHICK KOMBINASI 500 GR (EXTRA1)
152 13012210 ASIMO SSG CHICK KOMBINASI 1 KG (EXTR$A2)
153 13030101 FIESTA CHICK MEAT BALL 300 GR
154 13030102 FIESTA CHICK MEATBALL 500 GR
155 13030501 CHAMP CHICK MEATBALL 200 GR
156 13030502 CHAMP CHICK MEATBALL 500 GR
157 13050101 FIESTA SCB 250 GR
158 13050105 FIESTA CHICKEN SLICE 300 GR
159 13050106 FIESTA BEEF SLICE 300 GR
160 13070501 CHAMP BEEF SSG SERBAGUNA 150 GR
161 13070502 CHAMP BEEF SSG SERBAGUNA 375GR
162 13070505 CHAMP BEEF SSG GORENG 375 GR
163 13070506 CHAMP FRANKFURTER SSG 375GR
164 13100512 CHAMP CHICK SSG S/SANTAP ORIG 546GR (CAN)
165 13110504 CHAMP BEEF BALL 500GR
166 13170510 CHAMP BEEF BBQ SSG S/SANTAP 546GR (CAN)
167 15010101 FIESTA SHOESTRING 500 GR
168 15010102 FIESTA SHOESTRING 1000 GR
169 15010107 FIESTA FRENCH F SHOESTRING INSTITUSI 2KG
170 15020101 FIESTA STRAIGHT CUT 500 GR
171 15020102 FIESTA STRAIGHT CUT 1000 GR
172 15030101 FIESTA CRINKLE CUT 500 GR
173 15030102 FIESTA CRINKLE CUT 1000 GR
174 15040101 FIESTA BATTER COATED 500 GR
175 15040102 FIESTA BATTER COATED 1000 GR
176 16060103 FIESTA CHICK SIOMAY 900GR
177 16060113 FIESTA CHICK SIOMAY 180GR (NEW)
178 16060114 FIESTA GYOZA 180 GR (NEW)
179 16060119 FIESTA RTG SIOMAY 54 GR/PAC
180 16060120 FIESTA KEECHO 400 GR/PAC
181 16060121 FIESTA CHICKEN TOFU 400 GR/PAC (NEW)
182 16060503 CHAMP CHICK&FISH SIOMAY 180 GR (NEW)
183 17200109 FIESTA RTS C/TERIYAKI 300GR/PAC
184 17200110 FIESTA RTS C/RENDANG 300GR/PAC
185 17200111 FIESTA RTS C/W RUJAK SC 300GR/PAC
186 17200112 FIESTA RTS C/W S/MATAH 300GR/PAC
187 17210106 FIESTA RTS B/YAKINIKU 300GR/PAC
188 17210107 FIESTA RTS B/RENDANG 300GR/PAC
189 17210108 FIESTA RTS B/BLACKPEPPER 300GR/PAC
190 17210109 FIESTA RTS B/BULGOGI 300GR/PAC
191 18050102 FIESTA RTG BAKSO KEJU 60 GR/PAC
192 18050103 FIESTA RTG BAKSO BAKAR BBQ 60 GR/PAC
193 18050104 FIESTA RTG BEEF BALL WITH MENTAI LAVA 55GR
194 18050105 FIESTA RTG BEEF BALL WITH CHEESE LAVA 55GR
195 20010101 FIESTA CRISPY CRUMBS 200 GR
196 20010102 FIESTA TP ROTI PUTIH 200 GR
197 20040101 FIESTA RAMEN BEKU 570 GR/PAC
198 20120102 FIESTA T/B AYAM GORENG 80 GR
199 20120105 FIESTA T/B SERBAGUNA  80 GR
200 20120106 FIESTA T/B KREMES 80 GR
201 20120115 FIESTA RACIK AYAM GORENG 20 GR/PAC
202 20120116 FIESTA RACIK NASI GORENG 20 GR/PAC
203 21000123 FIESTA RICE W/GEPREK CHICKEN 320GR/PAC
204 21000124 FIESTA RICE W/CHICK RUJAK 320 GR/PAC
205 21000125 FIESTA RICE W/KOREAN BBQ CHICK 320 GR/PAC
206 21000126 NEW FIESTA CHICK RENDANG W RICE 320GR (PAC)
207 21000127 NEW FIESTA CHICK TERIYAKI W RICE 320GR (PAC)
208 21000128 NEW FIESTA CHICK TANDORI W RICE 320GR (PAC)
209 21000129 NEW FIESTA RICE W KARAGE&SSS 320GR (PAC)
210 21000130 NEW FIESTA RICE W/C CHEESE BULDAK 320GR (PAC)
211 21000131 NEW FIESTA RICE W CHIC CURRY 320GR (PAC)
212 21000132 NEW FIESTA RICE W CHICK DONBURI 320GR (PAC)
213 21000133 NEW FIESTA RICE W CHICK SATAY 320GR (PAC)
214 21000134 NEW FIESTA COCONUT RICE W SPICY CHICK 320GR (PAC)
215 21000135 NEW FIESTA RICE W POPBITES S/MATAH 320GR (PAC)
216 21000136 NEW FIESTA TUMERIC W POPBITES 320GR (PAC)
217 21000137 FIESTA HAINAMESE CHICKEN RICE 320GR (PAC)
218 21010101 FIESTA TRUFFLE GYUDON 320 GR/PAC
219 21010102 NEW FIESTA BEEF YAKINIKU W RICE 320GR (PAC)
220 21010103 NEW FIESTA BEEF BULGOGI W RICE 320GR (PAC)
221 21010104 NEW FIESTA BEEF RENDANG W RICE 320GR (PAC)
222 21010105 NEW FIESTA RICE W BEEF BLACKPEPPER 320GR (PAC)
223 21200107 NEW FIESTA SPAGHETTI CARBONARA 300GR (PAC)
224 21200108 NEW FIESTA SPAGHETTI CHIC BOLOGNESE 320GR (PAC)
225 21200109 NEW FIESTA ITALIAN MEATBALL SPAGHETTI 320GR (PAC)
226 21310103 NEW FIESTA SCB&S/SSG FRIED RICE 320GR (PAC)
227 21500101 FIESTA CHICK SSG & C. BALL PIZZA 230GR/PAC
228 21500102 FIESTA CHEESY BEEF PIZZA 230GR/PAC
229 91000012 PHOTOCARD RTG
230 1188002W PAHA ATAS 25-30 G FZ (*)
231 1195008A RTC CHICKEN KALASAN 400 GR (PAC)
232 1195008E RTC CHICKEN TERIYAKI 400 GR (PAC)
233 1195008X RTC CHICKEN SPICY 400 GR (PAC)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 543 KiB

After

Width:  |  Height:  |  Size: 546 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 829 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 929 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 768 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 875 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 751 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 857 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 827 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 748 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 832 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 824 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

File diff suppressed because it is too large. Load diff
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -14,7 +14,7 @@ class AppConfig {
// - Physical Device on same Wi-Fi: use 'http://192.168.70.7:8000/api/v1' // - Physical Device on same Wi-Fi: use 'http://192.168.70.7:8000/api/v1'
// - Android Emulator: use 'http://10.0.2.2:8000/api/v1' // - Android Emulator: use 'http://10.0.2.2:8000/api/v1'
// - iOS Simulator: use 'http://localhost:8000/api/v1' // - iOS Simulator: use 'http://localhost:8000/api/v1'
static const String _lanBaseUrl = 'http://192.168.70.7:8000/api/v1'; static const String _lanBaseUrl = 'http://192.168.70.5:8000/api/v1';
// Ngrok public tunnel URL (update ini setiap ngrok di-restart) // Ngrok public tunnel URL (update ini setiap ngrok di-restart)
static const String _ngrokBaseUrl = 'https://unlocated-waylon-potently.ngrok-free.dev/api/v1'; static const String _ngrokBaseUrl = 'https://unlocated-waylon-potently.ngrok-free.dev/api/v1';
+51
View File
@@ -0,0 +1,51 @@
# Get the active network interface IP address (excluding virtual and loopback adapters)
Write-Host "Checking active network adapters..." -ForegroundColor Cyan
$ipAddress = (Get-NetIPAddress -AddressFamily IPv4 | Where-Object {
$_.IPAddress -notlike "127.*" -and
$_.IPAddress -notlike "169.254.*" -and
$_.InterfaceAlias -notlike "*vEthernet*" -and
$_.InterfaceAlias -notlike "*WSL*" -and
$_.IPAddress -notlike "192.168.56.*"
} | Select-Object -ExpandProperty IPAddress -First 1)
if (-not $ipAddress) {
Write-Host "[-] No active LAN IP address found! Falling back to 127.0.0.1" -ForegroundColor Yellow
$ipAddress = "127.0.0.1"
} else {
Write-Host "[+] Found active Host IP: $ipAddress" -ForegroundColor Green
}
# Update Flutter Configuration
$configFile = "lib/config/app_config.dart"
if (Test-Path $configFile) {
$content = Get-Content $configFile -Raw
$pattern = "(static const String _lanBaseUrl = 'http://)([^:]+)(:8000/api/v1';)"
if ($content -match $pattern) {
$content = $content -replace $pattern, ('${1}' + $ipAddress + '${3}')
Set-Content $configFile $content -NoNewline
Write-Host "[+] Synchronized local IP address ($ipAddress) to Flutter configuration." -ForegroundColor Green
} else {
Write-Host "[-] Warning: _lanBaseUrl pattern not found in $configFile." -ForegroundColor Yellow
}
} else {
Write-Host "[-] Warning: Flutter config file '$configFile' not found. Skip updating local IP." -ForegroundColor Yellow
}
# Start ngrok
Write-Host "`nStarting Ngrok tunnel..." -ForegroundColor Cyan
Write-Host "Exposing: http://localhost:8000" -ForegroundColor Cyan
Write-Host "Public URL: https://unlocated-waylon-potently.ngrok-free.dev" -ForegroundColor Cyan
# Check if ngrok is available
$ngrokCmd = Get-Command ngrok -ErrorAction SilentlyContinue
if ($ngrokCmd) {
Write-Host "[+] Launching ngrok in a new window..." -ForegroundColor Green
Start-Process ngrok -ArgumentList "http", "--domain=unlocated-waylon-potently.ngrok-free.dev", "8000"
Write-Host "[+] Tunnel launched successfully!" -ForegroundColor Green
} else {
Write-Host "[-] Error: 'ngrok' command not found in your system PATH." -ForegroundColor Red
Write-Host " Please ensure ngrok is installed and added to your environment variables." -ForegroundColor Yellow
}
Write-Host "`nDone! Your Flutter app can now connect locally via WiFi ($ipAddress) or publicly via Ngrok." -ForegroundColor Green