Antaram commited on
Commit
3a639f4
·
verified ·
1 Parent(s): 99bdad7

Upload 20 files

Browse files
package.json CHANGED
@@ -7,6 +7,7 @@
7
  "start": "node src/server.js",
8
  "dev": "nodemon src/server.js",
9
  "migrate": "node src/db/migrate.js",
 
10
  "seed": "node src/db/seed.js",
11
  "reset": "node src/db/reset.js"
12
  },
 
7
  "start": "node src/server.js",
8
  "dev": "nodemon src/server.js",
9
  "migrate": "node src/db/migrate.js",
10
+ "migrate2": "node src/db/migrate2.js",
11
  "seed": "node src/db/seed.js",
12
  "reset": "node src/db/reset.js"
13
  },
src/db/config.js CHANGED
@@ -26,4 +26,4 @@ pool.on('error', (err) => {
26
  process.exit(-1);
27
  });
28
 
29
- module.exports = pool;
 
26
  process.exit(-1);
27
  });
28
 
29
+ module.exports = pool;
src/db/migrate.js CHANGED
@@ -17,12 +17,18 @@ const createTables = async () => {
17
  city VARCHAR(100),
18
  party_type VARCHAR(20) NOT NULL CHECK (party_type IN ('awaak', 'jawaak', 'both')),
19
  current_balance DECIMAL(12, 2) DEFAULT 0,
 
20
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
21
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
22
  )
23
  `);
24
  console.log('✅ Created table: parties');
25
 
 
 
 
 
 
26
  // 2. Create mirchi_types table
27
  await client.query(`
28
  CREATE TABLE IF NOT EXISTS mirchi_types (
 
17
  city VARCHAR(100),
18
  party_type VARCHAR(20) NOT NULL CHECK (party_type IN ('awaak', 'jawaak', 'both')),
19
  current_balance DECIMAL(12, 2) DEFAULT 0,
20
+ past_due DECIMAL(12, 2) DEFAULT 0,
21
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
22
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
23
  )
24
  `);
25
  console.log('✅ Created table: parties');
26
 
27
+ await client.query(`
28
+ ALTER TABLE parties
29
+ ADD COLUMN IF NOT EXISTS past_due DECIMAL(12, 2) DEFAULT 0
30
+ `);
31
+
32
  // 2. Create mirchi_types table
33
  await client.query(`
34
  CREATE TABLE IF NOT EXISTS mirchi_types (
src/db/migrate2.js ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const pool = require('./config');
2
+
3
+ const migrate2 = async () => {
4
+ const client = await pool.connect();
5
+
6
+ try {
7
+ console.log('🚀 Starting database migration (migrate2)...');
8
+
9
+ await client.query('BEGIN');
10
+
11
+ // Add past_due (opening balance) to parties
12
+ await client.query(`
13
+ ALTER TABLE parties
14
+ ADD COLUMN IF NOT EXISTS past_due DECIMAL(12, 2) DEFAULT 0
15
+ `);
16
+
17
+ // Ensure no NULL values exist
18
+ await client.query(`
19
+ UPDATE parties
20
+ SET past_due = 0
21
+ WHERE past_due IS NULL
22
+ `);
23
+
24
+ await client.query('COMMIT');
25
+ console.log('✅ migrate2 completed successfully!');
26
+
27
+ } catch (error) {
28
+ await client.query('ROLLBACK');
29
+ console.error('❌ migrate2 failed:', error);
30
+ throw error;
31
+ } finally {
32
+ client.release();
33
+ await pool.end();
34
+ }
35
+ };
36
+
37
+ migrate2().catch(err => {
38
+ console.error('Fatal error:', err);
39
+ process.exit(1);
40
+ });
src/routes/parties.js CHANGED
@@ -41,7 +41,7 @@ router.post('/', async (req, res) => {
41
  const client = await pool.connect();
42
 
43
  try {
44
- const { id, name, phone, city, party_type, current_balance } = req.body;
45
 
46
  if (!name) {
47
  return res.status(400).json({ success: false, message: 'Party name is required' });
@@ -59,18 +59,18 @@ router.post('/', async (req, res) => {
59
  // Update existing party
60
  result = await client.query(
61
  `UPDATE parties
62
- SET name = $1, phone = $2, city = $3, party_type = $4, current_balance = $5, updated_at = CURRENT_TIMESTAMP
63
- WHERE id = $6
64
  RETURNING *`,
65
- [name, phone || '', city || '', party_type || 'both', current_balance || 0, partyId]
66
  );
67
  } else {
68
  // Insert new party
69
  result = await client.query(
70
- `INSERT INTO parties (id, name, phone, city, party_type, current_balance)
71
- VALUES ($1, $2, $3, $4, $5, $6)
72
  RETURNING *`,
73
- [partyId, name, phone || '', city || '', party_type || 'both', current_balance || 0]
74
  );
75
  }
76
 
 
41
  const client = await pool.connect();
42
 
43
  try {
44
+ const { id, name, phone, city, party_type, current_balance, past_due } = req.body;
45
 
46
  if (!name) {
47
  return res.status(400).json({ success: false, message: 'Party name is required' });
 
59
  // Update existing party
60
  result = await client.query(
61
  `UPDATE parties
62
+ SET name = $1, phone = $2, city = $3, party_type = $4, current_balance = $5, past_due = $6, updated_at = CURRENT_TIMESTAMP
63
+ WHERE id = $7
64
  RETURNING *`,
65
+ [name, phone || '', city || '', party_type || 'both', current_balance || 0, past_due || 0, partyId]
66
  );
67
  } else {
68
  // Insert new party
69
  result = await client.query(
70
+ `INSERT INTO parties (id, name, phone, city, party_type, current_balance, past_due)
71
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
72
  RETURNING *`,
73
+ [partyId, name, phone || '', city || '', party_type || 'both', current_balance || 0, past_due || 0]
74
  );
75
  }
76
 
src/routes/transactions.js CHANGED
@@ -530,4 +530,164 @@ router.patch('/:id/payment', async (req, res) => {
530
  }
531
  });
532
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
533
  module.exports = router;
 
530
  }
531
  });
532
 
533
+ // POST revert transaction (rollback stock + balances, then delete)
534
+ router.post('/:id/revert', async (req, res) => {
535
+ const client = await pool.connect();
536
+
537
+ try {
538
+ const { id } = req.params;
539
+
540
+ await client.query('BEGIN');
541
+
542
+ const txResult = await client.query(
543
+ 'SELECT * FROM transactions WHERE id = $1',
544
+ [id]
545
+ );
546
+
547
+ if (txResult.rows.length === 0) {
548
+ await client.query('ROLLBACK');
549
+ return res.status(404).json({ success: false, message: 'Transaction not found' });
550
+ }
551
+
552
+ const tx = txResult.rows[0];
553
+
554
+ const itemsResult = await client.query(
555
+ 'SELECT * FROM transaction_items WHERE transaction_id = $1',
556
+ [id]
557
+ );
558
+
559
+ const items = itemsResult.rows;
560
+
561
+ if (!items || items.length === 0) {
562
+ await client.query('ROLLBACK');
563
+ return res.status(400).json({ success: false, message: 'No items found for this transaction' });
564
+ }
565
+
566
+ // Rollback lots (inverse of creation logic)
567
+ for (const item of items) {
568
+ if (!item.lot_id) continue;
569
+
570
+ const lotResult = await client.query(
571
+ 'SELECT * FROM lots WHERE id = $1',
572
+ [item.lot_id]
573
+ );
574
+
575
+ if (lotResult.rows.length === 0) {
576
+ await client.query('ROLLBACK');
577
+ return res.status(400).json({ success: false, message: `Lot not found for item ${item.id}` });
578
+ }
579
+
580
+ const lot = lotResult.rows[0];
581
+ const netWeight = Number(item.net_weight || 0);
582
+ const grossWeight = Number(item.gross_weight || 0);
583
+
584
+ // AWAAK = Purchase (Stock IN), JAWAAK = Sale (Stock OUT)
585
+ if (tx.bill_type === 'awaak' && !tx.is_return) {
586
+ // Reverting a purchase: subtract stock that was added
587
+ if (Number(lot.remaining_quantity) < netWeight || Number(lot.total_quantity) < netWeight) {
588
+ await client.query('ROLLBACK');
589
+ return res.status(409).json({
590
+ success: false,
591
+ message: `Cannot revert: stock already used for lot ${lot.lot_number}. Remaining ${lot.remaining_quantity}, required ${netWeight}`
592
+ });
593
+ }
594
+
595
+ await client.query(
596
+ `UPDATE lots
597
+ SET total_quantity = total_quantity - $1,
598
+ remaining_quantity = remaining_quantity - $1,
599
+ status = CASE WHEN remaining_quantity - $1 <= 0 THEN 'sold_out' ELSE 'active' END,
600
+ updated_at = CURRENT_TIMESTAMP
601
+ WHERE id = $2`,
602
+ [netWeight, item.lot_id]
603
+ );
604
+ } else if (tx.bill_type === 'awaak' && tx.is_return) {
605
+ // Reverting a purchase return: add stock back
606
+ await client.query(
607
+ `UPDATE lots
608
+ SET total_quantity = total_quantity + $1,
609
+ remaining_quantity = remaining_quantity + $1,
610
+ status = 'active',
611
+ updated_at = CURRENT_TIMESTAMP
612
+ WHERE id = $2`,
613
+ [netWeight, item.lot_id]
614
+ );
615
+ } else if (tx.bill_type === 'jawaak' && !tx.is_return) {
616
+ // Reverting a sale: add stock back to remaining (gross)
617
+ await client.query(
618
+ `UPDATE lots
619
+ SET remaining_quantity = remaining_quantity + $1,
620
+ status = CASE WHEN remaining_quantity + $1 > 0 THEN 'active' ELSE status END,
621
+ updated_at = CURRENT_TIMESTAMP
622
+ WHERE id = $2`,
623
+ [grossWeight, item.lot_id]
624
+ );
625
+ } else if (tx.bill_type === 'jawaak' && tx.is_return) {
626
+ // Reverting a sales return: subtract stock that was added back (gross)
627
+ if (Number(lot.remaining_quantity) < grossWeight) {
628
+ await client.query('ROLLBACK');
629
+ return res.status(409).json({
630
+ success: false,
631
+ message: `Cannot revert: not enough remaining stock in lot ${lot.lot_number}. Remaining ${lot.remaining_quantity}, required ${grossWeight}`
632
+ });
633
+ }
634
+
635
+ await client.query(
636
+ `UPDATE lots
637
+ SET remaining_quantity = remaining_quantity - $1,
638
+ status = CASE WHEN remaining_quantity - $1 <= 0 THEN 'sold_out' ELSE 'active' END,
639
+ updated_at = CURRENT_TIMESTAMP
640
+ WHERE id = $2`,
641
+ [grossWeight, item.lot_id]
642
+ );
643
+ }
644
+ }
645
+
646
+ // Rollback party balance (inverse of creation logic in this file)
647
+ const balanceAmount = Number(tx.balance_amount || 0);
648
+
649
+ if (tx.bill_type === 'jawaak') {
650
+ if (tx.is_return) {
651
+ await client.query(
652
+ 'UPDATE parties SET current_balance = current_balance - $1 WHERE id = $2',
653
+ [balanceAmount, tx.party_id]
654
+ );
655
+ } else {
656
+ await client.query(
657
+ 'UPDATE parties SET current_balance = current_balance + $1 WHERE id = $2',
658
+ [balanceAmount, tx.party_id]
659
+ );
660
+ }
661
+ } else {
662
+ if (tx.is_return) {
663
+ await client.query(
664
+ 'UPDATE parties SET current_balance = current_balance + $1 WHERE id = $2',
665
+ [balanceAmount, tx.party_id]
666
+ );
667
+ } else {
668
+ await client.query(
669
+ 'UPDATE parties SET current_balance = current_balance - $1 WHERE id = $2',
670
+ [balanceAmount, tx.party_id]
671
+ );
672
+ }
673
+ }
674
+
675
+ // Delete transaction (items/expenses/payments will cascade delete)
676
+ await client.query('DELETE FROM transactions WHERE id = $1', [id]);
677
+
678
+ await client.query('COMMIT');
679
+
680
+ res.json({
681
+ success: true,
682
+ message: 'Transaction reverted successfully'
683
+ });
684
+ } catch (error) {
685
+ await client.query('ROLLBACK');
686
+ console.error('Error reverting transaction:', error);
687
+ res.status(500).json({ success: false, message: error.message });
688
+ } finally {
689
+ client.release();
690
+ }
691
+ });
692
+
693
  module.exports = router;