project-startup 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-startup",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Minimal session-based auth starter using Express, MySQL, React, Vite, and React Router.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -2,24 +2,19 @@ const db = require("../db");
2
2
 
3
3
  // GET /api/parts
4
4
  exports.getAllParts = async (req, res) => {
5
- const [parts] = await db.query(
6
- "SELECT * FROM spare_parts ORDER BY name ASC"
7
- );
5
+ const [parts] = await db.query("SELECT * FROM spare_parts ORDER BY name ASC");
8
6
  res.json(parts);
9
7
  };
10
8
 
11
9
  // GET /api/parts/:id
12
10
  exports.getPartById = async (req, res) => {
13
- const [rows] = await db.query(
14
- "SELECT * FROM spare_parts WHERE id = ?",
15
- [req.params.id]
16
- );
11
+ const [parts] = await db.query("SELECT * FROM spare_parts WHERE id = ?", [req.params.id]);
17
12
 
18
- if (rows.length === 0) {
19
- return res.status(404).json({ error: "Spare part not found." });
13
+ if (parts.length === 0) {
14
+ return res.status(404).json({ error: "Spare part not found" });
20
15
  }
21
16
 
22
- res.json(rows[0]);
17
+ res.json(parts[0]);
23
18
  };
24
19
 
25
20
  // POST /api/parts
@@ -27,11 +22,11 @@ exports.createPart = async (req, res) => {
27
22
  const { name, quantity, unitPrice } = req.body;
28
23
 
29
24
  if (!name || quantity == null || !unitPrice) {
30
- return res.status(400).json({ error: "name, quantity and unitPrice are required." });
25
+ return res.status(400).json({ error: "name, quantity and unitPrice required" });
31
26
  }
32
27
 
33
28
  if (quantity < 0) {
34
- return res.status(400).json({ error: "Quantity cannot be negative." });
29
+ return res.status(400).json({ error: "Quantity cannot be negative" });
35
30
  }
36
31
 
37
32
  const [result] = await db.query(
@@ -39,66 +34,39 @@ exports.createPart = async (req, res) => {
39
34
  [name.trim(), quantity, unitPrice]
40
35
  );
41
36
 
42
- const [rows] = await db.query(
43
- "SELECT * FROM spare_parts WHERE id = ?",
44
- [result.insertId]
45
- );
46
-
47
- res.status(201).json(rows[0]);
37
+ const [parts] = await db.query("SELECT * FROM spare_parts WHERE id = ?", [result.insertId]);
38
+ res.status(201).json(parts[0]);
48
39
  };
49
40
 
50
41
  // PUT /api/parts/:id
51
42
  exports.updatePart = async (req, res) => {
52
- const [rows] = await db.query(
53
- "SELECT * FROM spare_parts WHERE id = ?",
54
- [req.params.id]
55
- );
43
+ const [parts] = await db.query("SELECT * FROM spare_parts WHERE id = ?", [req.params.id]);
56
44
 
57
- if (rows.length === 0) {
58
- return res.status(404).json({ error: "Spare part not found." });
45
+ if (parts.length === 0) {
46
+ return res.status(404).json({ error: "Spare part not found" });
59
47
  }
60
48
 
61
49
  const { name, unitPrice } = req.body;
62
- // Note: quantity is managed by stock-in / stock-out, not edited directly
63
50
 
64
51
  await db.query(
65
52
  `UPDATE spare_parts SET
66
- name = COALESCE(?, name),
53
+ name = COALESCE(?, name),
67
54
  unit_price = COALESCE(?, unit_price)
68
55
  WHERE id = ?`,
69
56
  [name || null, unitPrice || null, req.params.id]
70
57
  );
71
58
 
72
- const [updated] = await db.query(
73
- "SELECT * FROM spare_parts WHERE id = ?",
74
- [req.params.id]
75
- );
76
-
59
+ const [updated] = await db.query("SELECT * FROM spare_parts WHERE id = ?", [req.params.id]);
77
60
  res.json(updated[0]);
78
61
  };
79
62
 
80
- // DELETE /api/parts/:id
63
+ // DELETE /api/parts/:id — SIMPLIFIED (removed stock-in check)
81
64
  exports.deletePart = async (req, res) => {
82
- // Block deletion if there are any stock-in records for this part
83
- const [stockIns] = await db.query(
84
- "SELECT id FROM stock_in WHERE spare_part_id = ?",
85
- [req.params.id]
86
- );
87
-
88
- if (stockIns.length > 0) {
89
- return res.status(400).json({
90
- error: "Cannot delete a part that has stock-in records.",
91
- });
92
- }
93
-
94
- const [result] = await db.query(
95
- "DELETE FROM spare_parts WHERE id = ?",
96
- [req.params.id]
97
- );
65
+ const [result] = await db.query("DELETE FROM spare_parts WHERE id = ?", [req.params.id]);
98
66
 
99
67
  if (result.affectedRows === 0) {
100
- return res.status(404).json({ error: "Spare part not found." });
68
+ return res.status(404).json({ error: "Spare part not found" });
101
69
  }
102
70
 
103
- res.json({ message: "Spare part deleted." });
104
- };
71
+ res.json({ message: "Spare part deleted" });
72
+ };
@@ -1,18 +1,10 @@
1
1
  const db = require("../db");
2
2
 
3
3
  // GET /api/stock-in
4
- // Returns all import records joined with part name
5
4
  exports.getAllStockIn = async (req, res) => {
6
5
  const [rows] = await db.query(
7
- `SELECT
8
- si.id,
9
- si.quantity,
10
- si.remaining_qty,
11
- si.unit_price,
12
- si.total_price,
13
- si.imported_at,
14
- sp.id AS spare_part_id,
15
- sp.name AS spare_part_name
6
+ `SELECT si.id, si.quantity, si.remaining_qty, si.unit_price, si.total_price,
7
+ si.imported_at, sp.id AS spare_part_id, sp.name AS spare_part_name
16
8
  FROM stock_in si
17
9
  JOIN spare_parts sp ON sp.id = si.spare_part_id
18
10
  ORDER BY si.imported_at DESC`
@@ -23,9 +15,7 @@ exports.getAllStockIn = async (req, res) => {
23
15
  // GET /api/stock-in/:id
24
16
  exports.getStockInById = async (req, res) => {
25
17
  const [rows] = await db.query(
26
- `SELECT
27
- si.*,
28
- sp.name AS spare_part_name
18
+ `SELECT si.*, sp.name AS spare_part_name
29
19
  FROM stock_in si
30
20
  JOIN spare_parts sp ON sp.id = si.spare_part_id
31
21
  WHERE si.id = ?`,
@@ -33,127 +23,74 @@ exports.getStockInById = async (req, res) => {
33
23
  );
34
24
 
35
25
  if (rows.length === 0) {
36
- return res.status(404).json({ error: "Stock-in record not found." });
26
+ return res.status(404).json({ error: "Stock-in record not found" });
37
27
  }
38
28
 
39
29
  res.json(rows[0]);
40
30
  };
41
31
 
42
- // POST /api/stock-in
43
- // Creates a new import batch and increments spare_parts.quantity
32
+ // POST /api/stock-in — SIMPLIFIED (no transaction)
44
33
  exports.createStockIn = async (req, res) => {
45
34
  const { sparePartId, quantity, unitPrice } = req.body;
46
35
 
47
36
  if (!sparePartId || !quantity || !unitPrice) {
48
- return res.status(400).json({
49
- error: "sparePartId, quantity and unitPrice are required.",
50
- });
37
+ return res.status(400).json({ error: "sparePartId, quantity and unitPrice required" });
51
38
  }
52
39
 
53
40
  if (quantity < 1) {
54
- return res.status(400).json({ error: "Quantity must be at least 1." });
41
+ return res.status(400).json({ error: "Quantity must be at least 1" });
55
42
  }
56
43
 
57
- // Verify the spare part exists
58
- const [partRows] = await db.query(
59
- "SELECT * FROM spare_parts WHERE id = ?",
60
- [sparePartId]
61
- );
62
-
63
- if (partRows.length === 0) {
64
- return res.status(404).json({ error: "Spare part not found." });
44
+ // Verify part exists
45
+ const [parts] = await db.query("SELECT * FROM spare_parts WHERE id = ?", [sparePartId]);
46
+ if (parts.length === 0) {
47
+ return res.status(404).json({ error: "Spare part not found" });
65
48
  }
66
49
 
67
50
  const totalPrice = quantity * unitPrice;
68
51
 
69
- const conn = await db.getConnection();
70
-
71
- try {
72
- await conn.beginTransaction();
73
-
74
- // Insert the stock-in batch
75
- // remaining_qty starts equal to quantity — decremented by stock-out later
76
- const [result] = await conn.query(
77
- `INSERT INTO stock_in
78
- (spare_part_id, quantity, remaining_qty, unit_price, total_price)
79
- VALUES (?, ?, ?, ?, ?)`,
80
- [sparePartId, quantity, quantity, unitPrice, totalPrice]
81
- );
82
-
83
- // Increment the part's total stock quantity
84
- await conn.query(
85
- "UPDATE spare_parts SET quantity = quantity + ? WHERE id = ?",
86
- [quantity, sparePartId]
87
- );
88
-
89
- await conn.commit();
90
-
91
- // Return the full record with part name
92
- const [rows] = await conn.query(
93
- `SELECT si.*, sp.name AS spare_part_name
94
- FROM stock_in si
95
- JOIN spare_parts sp ON sp.id = si.spare_part_id
96
- WHERE si.id = ?`,
97
- [result.insertId]
98
- );
99
-
100
- res.status(201).json(rows[0]);
101
-
102
- } catch (err) {
103
- await conn.rollback();
104
- throw err;
105
- } finally {
106
- conn.release();
107
- }
108
- };
52
+ // Insert stock-in batch
53
+ const [result] = await db.query(
54
+ `INSERT INTO stock_in (spare_part_id, quantity, remaining_qty, unit_price, total_price)
55
+ VALUES (?, ?, ?, ?, ?)`,
56
+ [sparePartId, quantity, quantity, unitPrice, totalPrice]
57
+ );
109
58
 
110
- // DELETE /api/stock-in/:id
111
- // Only allowed if no stock-out records reference this batch
112
- exports.deleteStockIn = async (req, res) => {
59
+ // Increment part's total stock
60
+ await db.query(
61
+ "UPDATE spare_parts SET quantity = quantity + ? WHERE id = ?",
62
+ [quantity, sparePartId]
63
+ );
64
+
65
+ // Return full record with part name
113
66
  const [rows] = await db.query(
114
- "SELECT * FROM stock_in WHERE id = ?",
115
- [req.params.id]
67
+ `SELECT si.*, sp.name AS spare_part_name
68
+ FROM stock_in si
69
+ JOIN spare_parts sp ON sp.id = si.spare_part_id
70
+ WHERE si.id = ?`,
71
+ [result.insertId]
116
72
  );
117
73
 
74
+ res.status(201).json(rows[0]);
75
+ };
76
+
77
+ // DELETE /api/stock-in/:id — SIMPLIFIED (no transaction, no stock-out check)
78
+ exports.deleteStockIn = async (req, res) => {
79
+ const [rows] = await db.query("SELECT * FROM stock_in WHERE id = ?", [req.params.id]);
80
+
118
81
  if (rows.length === 0) {
119
- return res.status(404).json({ error: "Stock-in record not found." });
82
+ return res.status(404).json({ error: "Stock-in record not found" });
120
83
  }
121
84
 
122
85
  const stockIn = rows[0];
123
86
 
124
- // Block deletion if sales came out of this batch
125
- const [sales] = await db.query(
126
- "SELECT id FROM stock_out WHERE stock_in_id = ?",
127
- [req.params.id]
87
+ // Give quantity back to spare part
88
+ await db.query(
89
+ "UPDATE spare_parts SET quantity = quantity - ? WHERE id = ?",
90
+ [stockIn.quantity, stockIn.spare_part_id]
128
91
  );
129
92
 
130
- if (sales.length > 0) {
131
- return res.status(400).json({
132
- error: "Cannot delete a batch that has stock-out records.",
133
- });
134
- }
135
-
136
- const conn = await db.getConnection();
137
-
138
- try {
139
- await conn.beginTransaction();
93
+ await db.query("DELETE FROM stock_in WHERE id = ?", [req.params.id]);
140
94
 
141
- // Give the quantity back to the spare part
142
- await conn.query(
143
- "UPDATE spare_parts SET quantity = quantity - ? WHERE id = ?",
144
- [stockIn.quantity, stockIn.spare_part_id]
145
- );
146
-
147
- await conn.query("DELETE FROM stock_in WHERE id = ?", [req.params.id]);
148
-
149
- await conn.commit();
150
-
151
- res.json({ message: "Stock-in record deleted and quantity reversed." });
152
-
153
- } catch (err) {
154
- await conn.rollback();
155
- throw err;
156
- } finally {
157
- conn.release();
158
- }
159
- };
95
+ res.json({ message: "Stock-in deleted and quantity reversed" });
96
+ };
@@ -3,19 +3,11 @@ const db = require("../db");
3
3
  // GET /api/stock-out
4
4
  exports.getAllStockOut = async (req, res) => {
5
5
  const [rows] = await db.query(
6
- `SELECT
7
- so.id,
8
- so.quantity,
9
- so.unit_price,
10
- so.total_price,
11
- so.sold_at,
12
- sp.id AS spare_part_id,
13
- sp.name AS spare_part_name,
14
- si.id AS stock_in_id,
15
- si.unit_price AS import_unit_price,
16
- si.imported_at
6
+ `SELECT so.id, so.quantity, so.unit_price, so.total_price, so.sold_at,
7
+ sp.id AS spare_part_id, sp.name AS spare_part_name,
8
+ si.id AS stock_in_id, si.unit_price AS import_unit_price, si.imported_at
17
9
  FROM stock_out so
18
- JOIN stock_in si ON si.id = so.stock_in_id
10
+ JOIN stock_in si ON si.id = so.stock_in_id
19
11
  JOIN spare_parts sp ON sp.id = so.spare_part_id
20
12
  ORDER BY so.sold_at DESC`
21
13
  );
@@ -25,159 +17,112 @@ exports.getAllStockOut = async (req, res) => {
25
17
  // GET /api/stock-out/:id
26
18
  exports.getStockOutById = async (req, res) => {
27
19
  const [rows] = await db.query(
28
- `SELECT
29
- so.*,
30
- sp.name AS spare_part_name,
31
- si.remaining_qty,
32
- si.unit_price AS import_unit_price
20
+ `SELECT so.*, sp.name AS spare_part_name, si.remaining_qty, si.unit_price AS import_unit_price
33
21
  FROM stock_out so
34
- JOIN stock_in si ON si.id = so.stock_in_id
22
+ JOIN stock_in si ON si.id = so.stock_in_id
35
23
  JOIN spare_parts sp ON sp.id = so.spare_part_id
36
24
  WHERE so.id = ?`,
37
25
  [req.params.id]
38
26
  );
39
27
 
40
28
  if (rows.length === 0) {
41
- return res.status(404).json({ error: "Stock-out record not found." });
29
+ return res.status(404).json({ error: "Stock-out record not found" });
42
30
  }
43
31
 
44
32
  res.json(rows[0]);
45
33
  };
46
34
 
47
- // POST /api/stock-out
48
- // Validates against stock_in.remaining_qty, then decrements both
49
- // stock_in.remaining_qty and spare_parts.quantity
35
+ // POST /api/stock-out — SIMPLIFIED (no transaction, no FOR UPDATE lock)
50
36
  exports.createStockOut = async (req, res) => {
51
37
  const { stockInId, quantity, unitPrice } = req.body;
52
38
 
53
39
  if (!stockInId || !quantity || !unitPrice) {
54
- return res.status(400).json({
55
- error: "stockInId, quantity and unitPrice are required.",
56
- });
40
+ return res.status(400).json({ error: "stockInId, quantity and unitPrice required" });
57
41
  }
58
42
 
59
43
  if (quantity < 1) {
60
- return res.status(400).json({ error: "Quantity must be at least 1." });
44
+ return res.status(400).json({ error: "Quantity must be at least 1" });
61
45
  }
62
46
 
63
- const conn = await db.getConnection();
64
-
65
- try {
66
- await conn.beginTransaction();
67
-
68
- // Lock the stock_in row so two concurrent sales can't oversell the batch
69
- const [siRows] = await conn.query(
70
- `SELECT si.*, sp.id AS part_id
71
- FROM stock_in si
72
- JOIN spare_parts sp ON sp.id = si.spare_part_id
73
- WHERE si.id = ?
74
- FOR UPDATE`,
75
- [stockInId]
76
- );
77
-
78
- if (siRows.length === 0) {
79
- await conn.rollback();
80
- return res.status(404).json({ error: "Stock-in batch not found." });
81
- }
82
-
83
- const batch = siRows[0];
84
-
85
- // Quantity sold must not exceed what remains in this specific batch
86
- if (quantity > batch.remaining_qty) {
87
- await conn.rollback();
88
- return res.status(400).json({
89
- error: `Only ${batch.remaining_qty} unit(s) remaining in this batch.`,
90
- });
91
- }
92
-
93
- const totalPrice = quantity * unitPrice;
94
-
95
- // Insert the sale record
96
- const [result] = await conn.query(
97
- `INSERT INTO stock_out
98
- (stock_in_id, spare_part_id, quantity, unit_price, total_price)
99
- VALUES (?, ?, ?, ?, ?)`,
100
- [stockInId, batch.spare_part_id, quantity, unitPrice, totalPrice]
101
- );
102
-
103
- // Decrement remaining units in this import batch
104
- await conn.query(
105
- "UPDATE stock_in SET remaining_qty = remaining_qty - ? WHERE id = ?",
106
- [quantity, stockInId]
107
- );
108
-
109
- // Decrement the part's total stock
110
- await conn.query(
111
- "UPDATE spare_parts SET quantity = quantity - ? WHERE id = ?",
112
- [quantity, batch.spare_part_id]
113
- );
114
-
115
- await conn.commit();
116
-
117
- // Return full record with names attached
118
- const [rows] = await conn.query(
119
- `SELECT
120
- so.*,
121
- sp.name AS spare_part_name,
122
- si.unit_price AS import_unit_price,
123
- si.remaining_qty
124
- FROM stock_out so
125
- JOIN stock_in si ON si.id = so.stock_in_id
126
- JOIN spare_parts sp ON sp.id = so.spare_part_id
127
- WHERE so.id = ?`,
128
- [result.insertId]
129
- );
130
-
131
- res.status(201).json(rows[0]);
132
-
133
- } catch (err) {
134
- await conn.rollback();
135
- throw err;
136
- } finally {
137
- conn.release();
47
+ // Get the stock-in batch
48
+ const [siRows] = await db.query(
49
+ `SELECT si.*, sp.id AS part_id
50
+ FROM stock_in si
51
+ JOIN spare_parts sp ON sp.id = si.spare_part_id
52
+ WHERE si.id = ?`,
53
+ [stockInId]
54
+ );
55
+
56
+ if (siRows.length === 0) {
57
+ return res.status(404).json({ error: "Stock-in batch not found" });
138
58
  }
139
- };
140
59
 
141
- // DELETE /api/stock-out/:id
142
- // Reverses the sale: restores remaining_qty on the batch and quantity on the part
143
- exports.deleteStockOut = async (req, res) => {
144
- const [rows] = await db.query(
145
- "SELECT * FROM stock_out WHERE id = ?",
146
- [req.params.id]
147
- );
60
+ const batch = siRows[0];
148
61
 
149
- if (rows.length === 0) {
150
- return res.status(404).json({ error: "Stock-out record not found." });
62
+ // Check remaining quantity (simple check, no lock)
63
+ if (quantity > batch.remaining_qty) {
64
+ return res.status(400).json({
65
+ error: `Only ${batch.remaining_qty} unit(s) remaining in this batch`
66
+ });
151
67
  }
152
68
 
153
- const sale = rows[0];
154
- const conn = await db.getConnection();
69
+ const totalPrice = quantity * unitPrice;
155
70
 
156
- try {
157
- await conn.beginTransaction();
71
+ // Insert sale record
72
+ const [result] = await db.query(
73
+ `INSERT INTO stock_out (stock_in_id, spare_part_id, quantity, unit_price, total_price)
74
+ VALUES (?, ?, ?, ?, ?)`,
75
+ [stockInId, batch.part_id, quantity, unitPrice, totalPrice]
76
+ );
158
77
 
159
- // Restore the batch's remaining quantity
160
- await conn.query(
161
- "UPDATE stock_in SET remaining_qty = remaining_qty + ? WHERE id = ?",
162
- [sale.quantity, sale.stock_in_id]
163
- );
78
+ // Decrement remaining units in batch
79
+ await db.query(
80
+ "UPDATE stock_in SET remaining_qty = remaining_qty - ? WHERE id = ?",
81
+ [quantity, stockInId]
82
+ );
164
83
 
165
- // Restore the part's total stock
166
- await conn.query(
167
- "UPDATE spare_parts SET quantity = quantity + ? WHERE id = ?",
168
- [sale.quantity, sale.spare_part_id]
169
- );
84
+ // Decrement part's total stock
85
+ await db.query(
86
+ "UPDATE spare_parts SET quantity = quantity - ? WHERE id = ?",
87
+ [quantity, batch.part_id]
88
+ );
170
89
 
171
- await conn.query("DELETE FROM stock_out WHERE id = ?", [sale.id]);
90
+ // Return full record
91
+ const [rows] = await db.query(
92
+ `SELECT so.*, sp.name AS spare_part_name, si.unit_price AS import_unit_price, si.remaining_qty
93
+ FROM stock_out so
94
+ JOIN stock_in si ON si.id = so.stock_in_id
95
+ JOIN spare_parts sp ON sp.id = so.spare_part_id
96
+ WHERE so.id = ?`,
97
+ [result.insertId]
98
+ );
172
99
 
173
- await conn.commit();
100
+ res.status(201).json(rows[0]);
101
+ };
174
102
 
175
- res.json({ message: "Stock-out record deleted and quantity restored." });
103
+ // DELETE /api/stock-out/:id SIMPLIFIED (no transaction)
104
+ exports.deleteStockOut = async (req, res) => {
105
+ const [rows] = await db.query("SELECT * FROM stock_out WHERE id = ?", [req.params.id]);
176
106
 
177
- } catch (err) {
178
- await conn.rollback();
179
- throw err;
180
- } finally {
181
- conn.release();
107
+ if (rows.length === 0) {
108
+ return res.status(404).json({ error: "Stock-out record not found" });
182
109
  }
183
- };
110
+
111
+ const sale = rows[0];
112
+
113
+ // Restore batch's remaining quantity
114
+ await db.query(
115
+ "UPDATE stock_in SET remaining_qty = remaining_qty + ? WHERE id = ?",
116
+ [sale.quantity, sale.stock_in_id]
117
+ );
118
+
119
+ // Restore part's total stock
120
+ await db.query(
121
+ "UPDATE spare_parts SET quantity = quantity + ? WHERE id = ?",
122
+ [sale.quantity, sale.spare_part_id]
123
+ );
124
+
125
+ await db.query("DELETE FROM stock_out WHERE id = ?", [sale.id]);
126
+
127
+ res.json({ message: "Stock-out deleted and quantity restored" });
128
+ };