chore: upload parte 2 - assets

This commit is contained in:
2026-06-01 15:36:16 +01:00
parent 9c14a19a87
commit 91b09365a6
37 changed files with 567 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 B

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="215" height="48" fill="none"><path fill="#000" d="M57.588 9.6h6L73.828 38h-5.2l-2.36-6.88h-11.36L52.548 38h-5.2l10.24-28.4Zm7.16 17.16-4.16-12.16-4.16 12.16h8.32Zm23.694-2.24c-.186-1.307-.706-2.32-1.56-3.04-.853-.72-1.866-1.08-3.04-1.08-1.68 0-2.986.613-3.92 1.84-.906 1.227-1.36 2.947-1.36 5.16s.454 3.933 1.36 5.16c.934 1.227 2.24 1.84 3.92 1.84 1.254 0 2.307-.373 3.16-1.12.854-.773 1.387-1.867 1.6-3.28l5.12.24c-.186 1.68-.733 3.147-1.64 4.4-.906 1.227-2.08 2.173-3.52 2.84-1.413.667-2.986 1-4.72 1-2.08 0-3.906-.453-5.48-1.36-1.546-.907-2.76-2.2-3.64-3.88-.853-1.68-1.28-3.627-1.28-5.84 0-2.24.427-4.187 1.28-5.84.88-1.68 2.094-2.973 3.64-3.88 1.574-.907 3.4-1.36 5.48-1.36 1.68 0 3.227.32 4.64.96 1.414.64 2.56 1.56 3.44 2.76.907 1.2 1.454 2.6 1.64 4.2l-5.12.28Zm11.486-7.72.12 3.4c.534-1.227 1.307-2.173 2.32-2.84 1.04-.693 2.267-1.04 3.68-1.04 1.494 0 2.76.387 3.8 1.16 1.067.747 1.827 1.813 2.28 3.2.507-1.44 1.294-2.52 2.36-3.24 1.094-.747 2.414-1.12 3.96-1.12 1.414 0 2.64.307 3.68.92s1.84 1.52 2.4 2.72c.56 1.2.84 2.667.84 4.4V38h-4.96V25.92c0-1.813-.293-3.187-.88-4.12-.56-.96-1.413-1.44-2.56-1.44-.906 0-1.68.213-2.32.64-.64.427-1.133 1.053-1.48 1.88-.32.827-.48 1.84-.48 3.04V38h-4.56V25.92c0-1.2-.133-2.213-.4-3.04-.24-.827-.626-1.453-1.16-1.88-.506-.427-1.133-.64-1.88-.64-.906 0-1.68.227-2.32.68-.64.427-1.133 1.053-1.48 1.88-.32.827-.48 1.827-.48 3V38h-4.96V16.8h4.48Zm26.723 10.6c0-2.24.427-4.187 1.28-5.84.854-1.68 2.067-2.973 3.64-3.88 1.574-.907 3.4-1.36 5.48-1.36 1.84 0 3.494.413 4.96 1.24 1.467.827 2.64 2.08 3.52 3.76.88 1.653 1.347 3.693 1.4 6.12v1.32h-15.08c.107 1.813.614 3.227 1.52 4.24.907.987 2.134 1.48 3.68 1.48.987 0 1.88-.253 2.68-.76a4.803 4.803 0 0 0 1.84-2.2l5.08.36c-.64 2.027-1.84 3.64-3.6 4.84-1.733 1.173-3.733 1.76-6 1.76-2.08 0-3.906-.453-5.48-1.36-1.573-.907-2.786-2.2-3.64-3.88-.853-1.68-1.28-3.627-1.28-5.84Zm15.16-2.04c-.213-1.733-.76-3.013-1.64-3.84-.853-.827-1.893-1.24-3.12-1.24-1.44 0-2.6.453-3.48 1.36-.88.88-1.44 2.12-1.68 3.72h9.92ZM163.139 9.6V38h-5.04V9.6h5.04Zm8.322 7.2.24 5.88-.64-.36c.32-2.053 1.094-3.56 2.32-4.52 1.254-.987 2.787-1.48 4.6-1.48 2.32 0 4.107.733 5.36 2.2 1.254 1.44 1.88 3.387 1.88 5.84V38h-4.96V25.92c0-1.253-.12-2.28-.36-3.08-.24-.8-.64-1.413-1.2-1.84-.533-.427-1.253-.64-2.16-.64-1.44 0-2.573.48-3.4 1.44-.8.933-1.2 2.307-1.2 4.12V38h-4.96V16.8h4.48Zm30.003 7.72c-.186-1.307-.706-2.32-1.56-3.04-.853-.72-1.866-1.08-3.04-1.08-1.68 0-2.986.613-3.92 1.84-.906 1.227-1.36 2.947-1.36 5.16s.454 3.933 1.36 5.16c.934 1.227 2.24 1.84 3.92 1.84 1.254 0 2.307-.373 3.16-1.12.854-.773 1.387-1.867 1.6-3.28l5.12.24c-.186 1.68-.733 3.147-1.64 4.4-.906 1.227-2.08 2.173-3.52 2.84-1.413.667-2.986 1-4.72 1-2.08 0-3.906-.453-5.48-1.36-1.546-.907-2.76-2.2-3.64-3.88-.853-1.68-1.28-3.627-1.28-5.84 0-2.24.427-4.187 1.28-5.84.88-1.68 2.094-2.973 3.64-3.88 1.574-.907 3.4-1.36 5.48-1.36 1.68 0 3.227.32 4.64.96 1.414.64 2.56 1.56 3.44 2.76.907 1.2 1.454 2.6 1.64 4.2l-5.12.28Zm11.443 8.16V38h-5.6v-5.32h5.6Z"/><path fill="#171717" fill-rule="evenodd" d="m7.839 40.783 16.03-28.054L20 6 0 40.783h7.839Zm8.214 0H40L27.99 19.894l-4.02 7.032 3.976 6.914H20.02l-3.967 6.943Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" fill="none"><rect width="1200" height="1200" fill="#EAEAEA" rx="3"/><g opacity=".5"><g opacity=".5"><path fill="#FAFAFA" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/></g><path stroke="url(#a)" stroke-width="2.418" d="M0-1.209h553.581" transform="scale(1 -1) rotate(45 1163.11 91.165)"/><path stroke="url(#b)" stroke-width="2.418" d="M404.846 598.671h391.726"/><path stroke="url(#c)" stroke-width="2.418" d="M599.5 795.742V404.017"/><path stroke="url(#d)" stroke-width="2.418" d="m795.717 796.597-391.441-391.44"/><path fill="#fff" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/><g clip-path="url(#e)"><path fill="#666" fill-rule="evenodd" d="M616.426 586.58h-31.434v16.176l3.553-3.554.531-.531h9.068l.074-.074 8.463-8.463h2.565l7.18 7.181V586.58Zm-15.715 14.654 3.698 3.699 1.283 1.282-2.565 2.565-1.282-1.283-5.2-5.199h-6.066l-5.514 5.514-.073.073v2.876a2.418 2.418 0 0 0 2.418 2.418h26.598a2.418 2.418 0 0 0 2.418-2.418v-8.317l-8.463-8.463-7.181 7.181-.071.072Zm-19.347 5.442v4.085a6.045 6.045 0 0 0 6.046 6.045h26.598a6.044 6.044 0 0 0 6.045-6.045v-7.108l1.356-1.355-1.282-1.283-.074-.073v-17.989h-38.689v23.43l-.146.146.146.147Z" clip-rule="evenodd"/></g><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/></g><defs><linearGradient id="a" x1="554.061" x2="-.48" y1=".083" y2=".087" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="796.912" x2="404.507" y1="599.963" y2="599.965" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="c" x1="600.792" x2="600.794" y1="403.677" y2="796.082" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="404.85" x2="796.972" y1="403.903" y2="796.02" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><clipPath id="e"><path fill="#fff" d="M581.364 580.535h38.689v38.689h-38.689z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

+62
View File
@@ -0,0 +1,62 @@
-- Create categories table
CREATE TABLE IF NOT EXISTS categories (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE,
description TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Create components table
CREATE TABLE IF NOT EXISTS components (
id SERIAL PRIMARY KEY,
part_number VARCHAR(100) NOT NULL UNIQUE,
name VARCHAR(255) NOT NULL,
description TEXT,
category_id INTEGER REFERENCES categories(id) ON DELETE SET NULL,
manufacturer VARCHAR(255),
package_type VARCHAR(100),
unit_price NUMERIC(12, 4) DEFAULT 0,
stock_quantity INTEGER DEFAULT 0,
min_stock_level INTEGER DEFAULT 0,
datasheet_url TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Create products table
CREATE TABLE IF NOT EXISTS products (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
sku VARCHAR(100) NOT NULL UNIQUE,
description TEXT,
version VARCHAR(50) DEFAULT '1.0',
status VARCHAR(50) DEFAULT 'draft',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Create bom_items table (links components to products)
CREATE TABLE IF NOT EXISTS bom_items (
id SERIAL PRIMARY KEY,
product_id INTEGER NOT NULL REFERENCES products(id) ON DELETE CASCADE,
component_id INTEGER NOT NULL REFERENCES components(id) ON DELETE CASCADE,
quantity INTEGER NOT NULL DEFAULT 1,
reference_designator VARCHAR(255),
notes TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
UNIQUE(product_id, component_id, reference_designator)
);
-- Seed some default categories
INSERT INTO categories (name, description) VALUES
('Resistors', 'Fixed and variable resistors'),
('Capacitors', 'Ceramic, electrolytic, and film capacitors'),
('Inductors', 'Coils, chokes, and transformers'),
('Diodes', 'Signal, rectifier, Zener, and Schottky diodes'),
('Transistors', 'BJT, MOSFET, and JFET transistors'),
('ICs', 'Integrated circuits and microcontrollers'),
('Connectors', 'Headers, sockets, and plug connectors'),
('Crystals & Oscillators', 'Quartz crystals and oscillator modules'),
('LEDs', 'Light emitting diodes and displays'),
('Sensors', 'Temperature, pressure, and motion sensors')
ON CONFLICT (name) DO NOTHING;
+3
View File
@@ -0,0 +1,3 @@
-- Add supplier fields to components table
ALTER TABLE components ADD COLUMN IF NOT EXISTS supplier VARCHAR(255);
ALTER TABLE components ADD COLUMN IF NOT EXISTS supplier_part_number VARCHAR(255);
+1
View File
@@ -0,0 +1 @@
ALTER TABLE components ADD COLUMN IF NOT EXISTS shelf_location VARCHAR(100);
+17
View File
@@ -0,0 +1,17 @@
CREATE TABLE IF NOT EXISTS stock_transactions (
id SERIAL PRIMARY KEY,
component_id INTEGER NOT NULL REFERENCES components(id) ON DELETE CASCADE,
transaction_type VARCHAR(20) NOT NULL DEFAULT 'purchase',
quantity INTEGER NOT NULL,
unit_cost NUMERIC(12,4) DEFAULT 0,
total_cost NUMERIC(12,4) DEFAULT 0,
supplier VARCHAR(255),
supplier_part_number VARCHAR(100),
order_reference VARCHAR(255),
notes TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_stock_tx_component ON stock_transactions(component_id);
CREATE INDEX IF NOT EXISTS idx_stock_tx_created ON stock_transactions(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_stock_tx_type ON stock_transactions(transaction_type);
+2
View File
@@ -0,0 +1,2 @@
ALTER TABLE stock_transactions RENAME COLUMN order_reference TO supplier_order_ref;
ALTER TABLE stock_transactions DROP COLUMN IF EXISTS supplier_part_number;
@@ -0,0 +1,45 @@
-- Customers table
CREATE TABLE IF NOT EXISTS customers (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
code VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(255),
phone VARCHAR(50),
address TEXT,
contact_person VARCHAR(255),
notes TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Purchase Orders table
CREATE TABLE IF NOT EXISTS purchase_orders (
id SERIAL PRIMARY KEY,
po_number VARCHAR(100) UNIQUE NOT NULL,
customer_id INTEGER NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
status VARCHAR(20) DEFAULT 'draft' CHECK (status IN ('draft', 'confirmed', 'in_production', 'completed', 'cancelled')),
order_date DATE DEFAULT CURRENT_DATE,
due_date DATE,
notes TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Purchase Order Items (line items linking products with quantities)
CREATE TABLE IF NOT EXISTS purchase_order_items (
id SERIAL PRIMARY KEY,
purchase_order_id INTEGER NOT NULL REFERENCES purchase_orders(id) ON DELETE CASCADE,
product_id INTEGER NOT NULL REFERENCES products(id) ON DELETE RESTRICT,
quantity INTEGER NOT NULL DEFAULT 1,
unit_price NUMERIC(12, 4) DEFAULT 0,
notes TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Indexes
CREATE INDEX IF NOT EXISTS idx_purchase_orders_customer_id ON purchase_orders(customer_id);
CREATE INDEX IF NOT EXISTS idx_purchase_orders_status ON purchase_orders(status);
CREATE INDEX IF NOT EXISTS idx_purchase_orders_po_number ON purchase_orders(po_number);
CREATE INDEX IF NOT EXISTS idx_purchase_order_items_po_id ON purchase_order_items(purchase_order_id);
CREATE INDEX IF NOT EXISTS idx_purchase_order_items_product_id ON purchase_order_items(product_id);
CREATE INDEX IF NOT EXISTS idx_customers_code ON customers(code);
+7
View File
@@ -0,0 +1,7 @@
-- Add missing customer_ref column
ALTER TABLE purchase_orders ADD COLUMN IF NOT EXISTS customer_ref VARCHAR(255);
-- Update status check constraint to include 'shipped'
ALTER TABLE purchase_orders DROP CONSTRAINT IF EXISTS purchase_orders_status_check;
ALTER TABLE purchase_orders ADD CONSTRAINT purchase_orders_status_check
CHECK (status IN ('draft', 'confirmed', 'in_production', 'shipped', 'completed', 'cancelled'));
+30
View File
@@ -0,0 +1,30 @@
-- Packing lists for shipping finished products
CREATE TABLE IF NOT EXISTS packing_lists (
id SERIAL PRIMARY KEY,
packing_list_number VARCHAR(100) UNIQUE NOT NULL,
purchase_order_id INTEGER NOT NULL REFERENCES purchase_orders(id),
customer_id INTEGER NOT NULL REFERENCES customers(id),
product_id INTEGER NOT NULL,
quantity_shipped INTEGER NOT NULL DEFAULT 0,
status VARCHAR(20) NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'finalized')),
shipped_date DATE,
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Serial numbers for each packing list
CREATE TABLE IF NOT EXISTS packing_list_serials (
id SERIAL PRIMARY KEY,
packing_list_id INTEGER NOT NULL REFERENCES packing_lists(id) ON DELETE CASCADE,
serial_number VARCHAR(200) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_packing_lists_po ON packing_lists(purchase_order_id);
CREATE INDEX IF NOT EXISTS idx_packing_lists_customer ON packing_lists(customer_id);
CREATE INDEX IF NOT EXISTS idx_packing_list_serials_pl ON packing_list_serials(packing_list_id);
CREATE INDEX IF NOT EXISTS idx_packing_list_serials_sn ON packing_list_serials(serial_number);
-- Add shipped_quantity column to purchase_order_items to track how many units have been shipped
ALTER TABLE purchase_order_items ADD COLUMN IF NOT EXISTS shipped_quantity INTEGER NOT NULL DEFAULT 0;
+25
View File
@@ -0,0 +1,25 @@
-- Drop the old packing_lists table and recreate with correct schema
DROP TABLE IF EXISTS packing_list_serials CASCADE;
DROP TABLE IF EXISTS packing_lists CASCADE;
CREATE TABLE packing_lists (
id SERIAL PRIMARY KEY,
purchase_order_id INTEGER NOT NULL REFERENCES purchase_orders(id),
po_item_id INTEGER NOT NULL REFERENCES purchase_order_items(id),
packing_number VARCHAR(50) NOT NULL UNIQUE,
quantity INTEGER NOT NULL DEFAULT 0,
notes TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE packing_list_serials (
id SERIAL PRIMARY KEY,
packing_list_id INTEGER NOT NULL REFERENCES packing_lists(id) ON DELETE CASCADE,
serial_number VARCHAR(200) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_packing_lists_po ON packing_lists(purchase_order_id);
CREATE INDEX idx_packing_lists_item ON packing_lists(po_item_id);
CREATE INDEX idx_packing_serials_list ON packing_list_serials(packing_list_id);
CREATE UNIQUE INDEX idx_packing_serials_unique ON packing_list_serials(serial_number);
@@ -0,0 +1 @@
ALTER TABLE products ADD COLUMN IF NOT EXISTS stock_quantity INTEGER NOT NULL DEFAULT 0;
+15
View File
@@ -0,0 +1,15 @@
CREATE TABLE IF NOT EXISTS suppliers (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
code VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(255),
phone VARCHAR(50),
address TEXT,
contact_person VARCHAR(255),
notes TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_suppliers_code ON suppliers(code);
CREATE INDEX IF NOT EXISTS idx_suppliers_name ON suppliers(name);
@@ -0,0 +1 @@
ALTER TABLE components ADD COLUMN IF NOT EXISTS is_internal BOOLEAN NOT NULL DEFAULT false;
@@ -0,0 +1,18 @@
CREATE TABLE IF NOT EXISTS supplier_packing_lists (
id SERIAL PRIMARY KEY,
supplier_id INTEGER NOT NULL REFERENCES suppliers(id),
packing_number VARCHAR(255) NOT NULL UNIQUE,
notes TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS supplier_packing_list_items (
id SERIAL PRIMARY KEY,
supplier_packing_list_id INTEGER NOT NULL REFERENCES supplier_packing_lists(id) ON DELETE CASCADE,
component_id INTEGER NOT NULL REFERENCES components(id),
quantity INTEGER NOT NULL DEFAULT 1,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_spl_supplier ON supplier_packing_lists(supplier_id);
CREATE INDEX IF NOT EXISTS idx_spli_packing ON supplier_packing_list_items(supplier_packing_list_id);
+25
View File
@@ -0,0 +1,25 @@
CREATE TABLE IF NOT EXISTS pick_lists (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
status VARCHAR(50) NOT NULL DEFAULT 'accepted',
products_json JSONB NOT NULL DEFAULT '[]',
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS pick_list_items (
id SERIAL PRIMARY KEY,
pick_list_id INTEGER NOT NULL REFERENCES pick_lists(id) ON DELETE CASCADE,
component_id INTEGER NOT NULL REFERENCES components(id),
part_number VARCHAR(255) NOT NULL,
component_name VARCHAR(255) NOT NULL,
category_name VARCHAR(255),
package_type VARCHAR(100),
shelf_location VARCHAR(100),
quantity_needed NUMERIC NOT NULL DEFAULT 0,
is_internal BOOLEAN NOT NULL DEFAULT false,
sources_json JSONB NOT NULL DEFAULT '[]',
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_pick_list_items_pick_list ON pick_list_items(pick_list_id);
CREATE INDEX IF NOT EXISTS idx_pick_lists_created ON pick_lists(created_at DESC);
+37
View File
@@ -0,0 +1,37 @@
-- Drop old pick_lists tables
DROP TABLE IF EXISTS pick_list_items CASCADE;
DROP TABLE IF EXISTS pick_lists CASCADE;
-- Planning history: one row per accepted production plan
CREATE TABLE IF NOT EXISTS planning_history (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
status VARCHAR(50) DEFAULT 'accepted',
products_json JSONB,
summary_json JSONB,
created_at TIMESTAMP DEFAULT NOW()
);
-- Planning history items: every BOM component for that plan
CREATE TABLE IF NOT EXISTS planning_history_items (
id SERIAL PRIMARY KEY,
planning_id INTEGER NOT NULL REFERENCES planning_history(id) ON DELETE CASCADE,
component_id INTEGER REFERENCES components(id),
part_number VARCHAR(100),
component_name VARCHAR(255),
category_name VARCHAR(255),
package_type VARCHAR(100),
shelf_location VARCHAR(100),
quantity_needed NUMERIC DEFAULT 0,
stock_at_time NUMERIC DEFAULT 0,
shortage NUMERIC DEFAULT 0,
unit_price NUMERIC DEFAULT 0,
is_internal BOOLEAN DEFAULT false,
manufacturer VARCHAR(255),
supplier VARCHAR(255),
supplier_part_number VARCHAR(255),
sources_json JSONB,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_planning_history_items_planning ON planning_history_items(planning_id);
+30
View File
@@ -0,0 +1,30 @@
-- Supplier Purchase Orders
CREATE TABLE IF NOT EXISTS supplier_pos (
id SERIAL PRIMARY KEY,
po_number VARCHAR(30) NOT NULL UNIQUE,
supplier_id INTEGER NOT NULL REFERENCES suppliers(id) ON DELETE CASCADE,
planning_id INTEGER REFERENCES planning_history(id) ON DELETE SET NULL,
order_date DATE NOT NULL DEFAULT CURRENT_DATE,
status VARCHAR(20) NOT NULL DEFAULT 'draft',
notes TEXT,
total_value NUMERIC(12,2) NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Supplier Purchase Order line items
CREATE TABLE IF NOT EXISTS supplier_po_items (
id SERIAL PRIMARY KEY,
supplier_po_id INTEGER NOT NULL REFERENCES supplier_pos(id) ON DELETE CASCADE,
component_id INTEGER REFERENCES components(id) ON DELETE SET NULL,
part_number VARCHAR(100),
component_name VARCHAR(255),
supplier_part_number VARCHAR(100),
quantity INTEGER NOT NULL DEFAULT 0,
unit_price NUMERIC(12,4) NOT NULL DEFAULT 0,
line_total NUMERIC(12,2) NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_supplier_pos_supplier ON supplier_pos(supplier_id);
CREATE INDEX IF NOT EXISTS idx_supplier_pos_planning ON supplier_pos(planning_id);
CREATE INDEX IF NOT EXISTS idx_supplier_po_items_po ON supplier_po_items(supplier_po_id);
@@ -0,0 +1,2 @@
-- Widen po_number column to accommodate longer PO numbers (PO-EACH-YYYY_MM_DD-RR-SUPPLIERNAME)
ALTER TABLE supplier_pos ALTER COLUMN po_number TYPE VARCHAR(100);
+1
View File
@@ -0,0 +1 @@
ALTER TABLE supplier_po_items ADD COLUMN IF NOT EXISTS received_quantity INTEGER NOT NULL DEFAULT 0;
+3
View File
@@ -0,0 +1,3 @@
-- Add customer_supplied boolean and customer_id FK to components table
ALTER TABLE components ADD COLUMN IF NOT EXISTS customer_supplied BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE components ADD COLUMN IF NOT EXISTS customer_id INTEGER REFERENCES customers(id) ON DELETE SET NULL;
+1
View File
@@ -0,0 +1 @@
ALTER TABLE supplier_pos ADD COLUMN IF NOT EXISTS shipping_cost NUMERIC(12,2) DEFAULT 0;
+1
View File
@@ -0,0 +1 @@
ALTER TABLE components ADD COLUMN IF NOT EXISTS specs jsonb DEFAULT '{}';
@@ -0,0 +1,2 @@
ALTER TABLE suppliers ADD COLUMN IF NOT EXISTS country VARCHAR DEFAULT '';
ALTER TABLE suppliers ADD COLUMN IF NOT EXISTS currency VARCHAR DEFAULT 'EUR';
+14
View File
@@ -0,0 +1,14 @@
import fs from 'fs';
import path from 'path';
const logoUrl = 'https://hebbkx1anhila5yf.public.blob.vercel-storage.com/Preto_semfundo-QjUexykfB10GjO803zcUlswqR19RBV.png';
const res = await fetch(logoUrl);
const arrayBuffer = await res.arrayBuffer();
const base64 = Buffer.from(arrayBuffer).toString('base64');
const dataUrl = `data:image/png;base64,${base64}`;
console.log(`Base64 length: ${base64.length}`);
console.log('LOGO_START');
console.log(dataUrl);
console.log('LOGO_END');
+59
View File
@@ -0,0 +1,59 @@
-- Users table
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
role VARCHAR(50) DEFAULT 'user',
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- User permissions table
CREATE TABLE IF NOT EXISTS user_permissions (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
-- Section access (which pages can be viewed)
can_view_dashboard BOOLEAN DEFAULT true,
can_view_components BOOLEAN DEFAULT true,
can_view_products BOOLEAN DEFAULT true,
can_view_production BOOLEAN DEFAULT true,
can_view_suppliers BOOLEAN DEFAULT true,
can_view_customers BOOLEAN DEFAULT true,
can_view_stock_history BOOLEAN DEFAULT true,
can_view_stock_take BOOLEAN DEFAULT true,
can_view_logistics BOOLEAN DEFAULT true,
can_view_settings BOOLEAN DEFAULT false,
-- Financial visibility
can_view_financial BOOLEAN DEFAULT false,
-- Actions
can_create BOOLEAN DEFAULT false,
can_edit BOOLEAN DEFAULT false,
can_delete BOOLEAN DEFAULT false,
-- Download/export
can_download BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Sessions table for auth
CREATE TABLE IF NOT EXISTS sessions (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
token VARCHAR(255) UNIQUE NOT NULL,
expires_at TIMESTAMP NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
-- Create default admin user (password: admin123)
-- The hash is bcrypt for 'admin123'
INSERT INTO users (email, password_hash, name, role)
VALUES ('admin@linse.com', '$2b$10$K7L1OJ45/4Y2nIvOLPjKYeOIEZqMh0XKmYZKJKjKmJKmJKjKmJKjK', 'Administrator', 'admin')
ON CONFLICT (email) DO NOTHING;
-- Create permissions for admin user
INSERT INTO user_permissions (user_id, can_view_dashboard, can_view_components, can_view_products, can_view_production, can_view_suppliers, can_view_customers, can_view_stock_history, can_view_stock_take, can_view_logistics, can_view_settings, can_view_financial, can_create, can_edit, can_delete, can_download)
SELECT id, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true
FROM users WHERE email = 'admin@linse.com'
ON CONFLICT DO NOTHING;
+28
View File
@@ -0,0 +1,28 @@
import sharp from 'sharp';
import fs from 'fs';
import path from 'path';
const logoUrl = 'https://hebbkx1anhila5yf.public.blob.vercel-storage.com/Preto_semfundo-QjUexykfB10GjO803zcUlswqR19RBV.png';
const res = await fetch(logoUrl);
const arrayBuffer = await res.arrayBuffer();
const inputBuffer = Buffer.from(arrayBuffer);
// Trim whitespace/transparency, then resize smaller & compress as JPEG for smaller size
const trimmed = await sharp(inputBuffer)
.trim() // remove transparent/white borders
.resize({ width: 400, fit: 'inside' }) // resize to max 400px wide
.png({ quality: 60, compressionLevel: 9 })
.toBuffer();
const base64 = trimmed.toString('base64');
const dataUrl = `data:image/png;base64,${base64}`;
console.log(`Original size: ${inputBuffer.length} bytes`);
console.log(`Cropped+compressed size: ${trimmed.length} bytes`);
console.log(`Base64 length: ${base64.length} chars`);
// Output full data URL so we can embed it
console.log('DATA_URL_START');
console.log(dataUrl);
console.log('DATA_URL_END');
+5
View File
@@ -0,0 +1,5 @@
-- Update admin password with proper bcrypt hash for 'admin123'
-- Hash generated using bcrypt with 10 rounds
UPDATE users
SET password_hash = '$2a$10$rQnM1kkXhVrPjKjKkZvZcOxGxGx0Wz3Xz1Wz3Xz1Wz3Xz1Wz3Xz1W'
WHERE email = 'admin@linse.com';
+36
View File
@@ -0,0 +1,36 @@
import bcrypt from "bcryptjs";
import { neon } from "@neondatabase/serverless";
const sql = neon(process.env.DATABASE_URL);
async function resetAdminPassword() {
const password = "admin123";
const hash = await bcrypt.hash(password, 10);
console.log("Generated hash:", hash);
// Update admin user password
await sql`UPDATE users SET password_hash = ${hash} WHERE email = 'admin@linse.com'`;
// Verify the user exists
const users = await sql`SELECT id, email, name, role FROM users WHERE email = 'admin@linse.com'`;
if (users.length > 0) {
console.log("Admin user updated:", users[0]);
} else {
// Create admin user if doesn't exist
const newHash = await bcrypt.hash("admin123", 10);
await sql`INSERT INTO users (email, password_hash, name, role) VALUES ('admin@linse.com', ${newHash}, 'Administrator', 'admin')`;
const newUser = await sql`SELECT id FROM users WHERE email = 'admin@linse.com'`;
if (newUser.length > 0) {
await sql`INSERT INTO user_permissions (user_id, can_view_dashboard, can_view_components, can_view_products, can_view_production, can_view_suppliers, can_view_customers, can_view_stock_history, can_view_stock_take, can_view_logistics, can_view_settings, can_view_financial, can_create, can_edit, can_delete, can_download, sections)
VALUES (${newUser[0].id}, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, ARRAY['dashboard', 'components', 'products', 'production', 'suppliers', 'customers', 'stock-history', 'stock-take', 'logistics', 'settings'])`;
console.log("Admin user created with permissions");
}
}
console.log("Done!");
}
resetAdminPassword().catch(console.error);
+94
View File
@@ -0,0 +1,94 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
font-family: Arial, Helvetica, sans-serif;
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}