Antaram commited on
Commit
8f23197
·
verified ·
1 Parent(s): 9eab1a6

Upload 25 files

Browse files
package.json CHANGED
@@ -7,6 +7,11 @@
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
+ "migrate3": "node src/db/migrate3.js",
12
+ "migrate4": "node src/db/migrate4.js",
13
+ "migrate5": "node src/db/migrate5.js",
14
+ "undo_migrate5": "node src/db/undo_migrate5.js",
15
  "seed": "node src/db/seed.js",
16
  "reset": "node src/db/reset.js"
17
  },
src/db/config.js CHANGED
@@ -1,48 +1,22 @@
1
  const { Pool } = require('pg');
 
 
2
  require('dotenv').config();
3
 
4
  const config = {
5
  user: process.env.DB_USER || "avnadmin",
6
  password: process.env.DB_PASSWORD || "AVNS_8YN2lShyUCy56qezi-_",
7
- // Updated Host
8
  host: process.env.DB_HOST || "staging-photobrex-3b7a.j.aivencloud.com",
9
- // Updated Port
10
  port: parseInt(process.env.DB_PORT || "10838"),
11
  database: process.env.DB_NAME || "defaultdb",
12
  ssl: {
13
- rejectUnauthorized: true,
14
- // Updated CA Certificate (Hardcoded string as requested)
15
- ca: `-----BEGIN CERTIFICATE-----
16
- MIIEUDCCArigAwIBAgIUNcDCtq9UKVDahrOq1uPUfByWsZswDQYJKoZIhvcNAQEM
17
- BQAwQDE+MDwGA1UEAww1NDM0MmMxOTktZWEzOC00ODZmLTg3ZmItNjkxMTQ0MTJh
18
- YzQ4IEdFTiAxIFByb2plY3QgQ0EwHhcNMjUxMTI5MTMwODM5WhcNMzUxMTI3MTMw
19
- ODM5WjBAMT4wPAYDVQQDDDU0MzQyYzE5OS1lYTM4LTQ4NmYtODdmYi02OTExNDQx
20
- MmFjNDggR0VOIDEgUHJvamVjdCBDQTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCC
21
- AYoCggGBAL4gSkKaQbOQz4eI1J3GfTTlG7BeFCQkxXD6M/ONZQY0CqLiOuIqQkcs
22
- qucBnZVk8ZqWRMqJZel+FhpUkk7Ddyole1m/DNnUh+ofO53R0ZnfrNmgqFlUkolc
23
- +VydiwgiprYUhne0G45Kjh87H4n73kbB+KsF5RkFXVwO4OO1AyrlUayUXhYyOzUg
24
- dP/NMupxGlL2rIlvfA1aVbEGwODmOkyGCvUHIEkytymhrXdkti6KX/M+9eM/ggKG
25
- zf4Ie4PTYXgWUxmRZsalwV1YlukUiwKS3X8SR0RhttWYopQmvVUhXYA0B8XHahcX
26
- 4M57Wh2DIDHIMTRqK8cUvlLYEzMAmPW0SiYiMUITkdeqM7TgHr7qyPUzfurEQ4ew
27
- 2RnZYbvBI/Yv5RG+2Wn5MDC1ccvfP6MXl+OdGf7GRcpCvJHaybzJy+7Hxj0LPc61
28
- h4XawJxtwlzxpdmZKbue/McqC/N0nKOQeVQxylvLKRUH29kKyNvnduCsLfYfVq5i
29
- iqOTrMQucwIDAQABo0IwQDAdBgNVHQ4EFgQUJmZTt7IsEdNVgLLHfDqMdm9F3mgw
30
- EgYDVR0TAQH/BAgwBgEB/wIBADALBgNVHQ8EBAMCAQYwDQYJKoZIhvcNAQEMBQAD
31
- ggGBALgLPFv1GMVta1W9O7ukFTqLFGAwDVG5FI7z+M6lSwhAoQat4gaSdpCc2NsS
32
- JpsVnF2xd+N2DTkg/pJs4iq1HQ2s0OwyrcWd4Z+1T4h5YN4V1gWbwBatXHUNHPEB
33
- 2/SrS4LSLFxiYmDseU8bvjYcsBy6GlUesA+EN6M6t5tk9IhZt4wLziq/0heMivHW
34
- BMZGbVFJYqSFL/PVgqXCH5wCO5dSVhnkV6vFoGVYZCQPnQXC9DC84cUrX73P7rLj
35
- mgO3O+iOwjy++G219WIn3Ek2TWINFsNBZUlLCWpuYsxCaRl06Z9v0bni7BneGWYw
36
- smzdMUQWrprCD/czqse0BgXDHvbydj0lDFahixQ6Wt0PK5pcXwa+YouqiPecdflW
37
- OSDAMYM5O8s9KLe2D7qGyxhBNT55ljsMx4hEbMmuoyYTYSrs0THknSFppso0407e
38
- 7CK61QMJWHzszde0njyBMTTcysJ1Dv89826h22dMNd5z97hGCmexNlfLB9v8DsoK
39
- Bj8KGw==
40
- -----END CERTIFICATE-----`,
41
  },
42
  };
43
 
44
  const pool = new Pool(config);
45
 
 
46
  pool.on('connect', () => {
47
  console.log('✅ Connected to PostgreSQL database');
48
  });
 
1
  const { Pool } = require('pg');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
  require('dotenv').config();
5
 
6
  const config = {
7
  user: process.env.DB_USER || "avnadmin",
8
  password: process.env.DB_PASSWORD || "AVNS_8YN2lShyUCy56qezi-_",
 
9
  host: process.env.DB_HOST || "staging-photobrex-3b7a.j.aivencloud.com",
 
10
  port: parseInt(process.env.DB_PORT || "10838"),
11
  database: process.env.DB_NAME || "defaultdb",
12
  ssl: {
13
+ rejectUnauthorized: false,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  },
15
  };
16
 
17
  const pool = new Pool(config);
18
 
19
+
20
  pool.on('connect', () => {
21
  console.log('✅ Connected to PostgreSQL database');
22
  });
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/db/migrate3.js ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const pool = require('./config');
2
+
3
+ const migrate3 = async () => {
4
+ const client = await pool.connect();
5
+
6
+ try {
7
+ console.log('🚀 Starting database migration (migrate3)...');
8
+
9
+ await client.query('BEGIN');
10
+
11
+ // Standalone Jama entries (overall party receipts) - not linked to bills
12
+ await client.query(`
13
+ CREATE TABLE IF NOT EXISTS party_jama_entries (
14
+ id VARCHAR(50) PRIMARY KEY,
15
+ party_id VARCHAR(50) NOT NULL REFERENCES parties(id) ON DELETE CASCADE,
16
+ entry_date DATE NOT NULL DEFAULT CURRENT_DATE,
17
+ amount DECIMAL(12, 2) NOT NULL,
18
+ note VARCHAR(255),
19
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
20
+ )
21
+ `);
22
+
23
+ await client.query(`
24
+ CREATE INDEX IF NOT EXISTS idx_party_jama_entries_party_id
25
+ ON party_jama_entries(party_id)
26
+ `);
27
+
28
+ await client.query(`
29
+ CREATE INDEX IF NOT EXISTS idx_party_jama_entries_entry_date
30
+ ON party_jama_entries(entry_date)
31
+ `);
32
+
33
+ await client.query('COMMIT');
34
+ console.log('✅ migrate3 completed successfully!');
35
+
36
+ } catch (error) {
37
+ await client.query('ROLLBACK');
38
+ console.error('❌ migrate3 failed:', error);
39
+ throw error;
40
+ } finally {
41
+ client.release();
42
+ await pool.end();
43
+ }
44
+ };
45
+
46
+ migrate3().catch(err => {
47
+ console.error('Fatal error:', err);
48
+ process.exit(1);
49
+ });
src/db/migrate4.js ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const pool = require('./config');
2
+
3
+ const MIGRATION_KEY = 'migrate4_recompute_party_current_balance';
4
+
5
+ const migrate4 = async () => {
6
+ const client = await pool.connect();
7
+
8
+ try {
9
+ console.log('🚀 Starting database migration (migrate4)...');
10
+
11
+ await client.query('BEGIN');
12
+
13
+ await client.query(`
14
+ CREATE TABLE IF NOT EXISTS migration_history (
15
+ key VARCHAR(120) PRIMARY KEY,
16
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
17
+ )
18
+ `);
19
+
20
+ const already = await client.query('SELECT key FROM migration_history WHERE key = $1', [MIGRATION_KEY]);
21
+ if (already.rows.length > 0) {
22
+ await client.query('COMMIT');
23
+ console.log('✅ migrate4 already applied. Skipping.');
24
+ return;
25
+ }
26
+
27
+ await client.query(`
28
+ UPDATE parties p
29
+ SET current_balance = (
30
+ COALESCE((
31
+ SELECT SUM(
32
+ CASE
33
+ WHEN t.bill_type = 'jawaak' AND COALESCE(t.is_return, false) = false THEN COALESCE(t.balance_amount, 0)
34
+ WHEN t.bill_type = 'jawaak' AND COALESCE(t.is_return, false) = true THEN -COALESCE(t.balance_amount, 0)
35
+ WHEN t.bill_type = 'awaak' AND COALESCE(t.is_return, false) = false THEN -COALESCE(t.balance_amount, 0)
36
+ WHEN t.bill_type = 'awaak' AND COALESCE(t.is_return, false) = true THEN COALESCE(t.balance_amount, 0)
37
+ ELSE 0
38
+ END
39
+ )
40
+ FROM transactions t
41
+ WHERE t.party_id = p.id
42
+ ), 0)
43
+ - COALESCE((
44
+ SELECT SUM(COALESCE(j.amount, 0))
45
+ FROM party_jama_entries j
46
+ WHERE j.party_id = p.id
47
+ ), 0)
48
+ ),
49
+ updated_at = CURRENT_TIMESTAMP
50
+ `);
51
+
52
+ await client.query('INSERT INTO migration_history(key) VALUES ($1)', [MIGRATION_KEY]);
53
+
54
+ await client.query('COMMIT');
55
+ console.log('✅ migrate4 completed successfully!');
56
+
57
+ } catch (error) {
58
+ await client.query('ROLLBACK');
59
+ console.error('❌ migrate4 failed:', error);
60
+ throw error;
61
+ } finally {
62
+ client.release();
63
+ await pool.end();
64
+ }
65
+ };
66
+
67
+ migrate4().catch(err => {
68
+ console.error('Fatal error:', err);
69
+ process.exit(1);
70
+ });
src/db/migrate5.js ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const pool = require('./config');
2
+
3
+ const migrate5 = async () => {
4
+ const client = await pool.connect();
5
+
6
+ try {
7
+ console.log('🚀 Starting database migration (migrate5)...');
8
+
9
+ await client.query('BEGIN');
10
+
11
+ await client.query(`
12
+ CREATE TABLE IF NOT EXISTS patti_lots (
13
+ id VARCHAR(50) PRIMARY KEY,
14
+ lot_number VARCHAR(100) NOT NULL UNIQUE,
15
+ mirchi_type_id VARCHAR(50) NOT NULL REFERENCES mirchi_types(id) ON DELETE CASCADE,
16
+ mirchi_name VARCHAR(100) NOT NULL,
17
+ total_quantity DECIMAL(10, 2) NOT NULL,
18
+ remaining_quantity DECIMAL(10, 2) NOT NULL,
19
+ owner_party_id VARCHAR(50) REFERENCES parties(id) ON DELETE SET NULL,
20
+ purchase_date DATE NOT NULL,
21
+ status VARCHAR(20) NOT NULL CHECK (status IN ('active', 'sold_out')),
22
+ avg_rate DECIMAL(10, 2) NOT NULL,
23
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
24
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
25
+ )
26
+ `);
27
+
28
+ await client.query(`
29
+ CREATE TABLE IF NOT EXISTS patti_transactions (
30
+ id VARCHAR(50) PRIMARY KEY,
31
+ bill_number VARCHAR(100) NOT NULL,
32
+ bill_date DATE NOT NULL,
33
+ bill_type VARCHAR(30) NOT NULL CHECK (bill_type IN ('patti_awaak', 'patti_jawaak')),
34
+ is_return BOOLEAN DEFAULT FALSE,
35
+ party_id VARCHAR(50) NOT NULL REFERENCES parties(id) ON DELETE CASCADE,
36
+ party_name VARCHAR(255),
37
+ invoice_group_id VARCHAR(80),
38
+ gross_weight_total DECIMAL(10, 2) DEFAULT 0,
39
+ net_weight_total DECIMAL(10, 2) DEFAULT 0,
40
+ subtotal DECIMAL(12, 2) DEFAULT 0,
41
+ total_expenses DECIMAL(12, 2) DEFAULT 0,
42
+ total_amount DECIMAL(12, 2) NOT NULL,
43
+ paid_amount DECIMAL(12, 2) DEFAULT 0,
44
+ balance_amount DECIMAL(12, 2) DEFAULT 0,
45
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
46
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
47
+ )
48
+ `);
49
+
50
+ await client.query(`
51
+ ALTER TABLE transactions
52
+ ADD COLUMN IF NOT EXISTS invoice_group_id VARCHAR(80)
53
+ `);
54
+
55
+ await client.query(`
56
+ CREATE TABLE IF NOT EXISTS patti_transaction_items (
57
+ id VARCHAR(50) PRIMARY KEY,
58
+ transaction_id VARCHAR(50) NOT NULL REFERENCES patti_transactions(id) ON DELETE CASCADE,
59
+ mirchi_type_id VARCHAR(50) NOT NULL REFERENCES mirchi_types(id),
60
+ mirchi_name VARCHAR(100),
61
+ quality VARCHAR(50),
62
+ lot_id VARCHAR(50) REFERENCES patti_lots(id),
63
+ lot_number VARCHAR(100),
64
+ poti_weights TEXT,
65
+ gross_weight DECIMAL(10, 2) NOT NULL,
66
+ poti_count INTEGER NOT NULL,
67
+ total_potya DECIMAL(10, 2) DEFAULT 0,
68
+ net_weight DECIMAL(10, 2) NOT NULL,
69
+ rate_per_kg DECIMAL(10, 2) NOT NULL,
70
+ item_total DECIMAL(12, 2) NOT NULL,
71
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
72
+ )
73
+ `);
74
+
75
+ await client.query(`
76
+ CREATE TABLE IF NOT EXISTS patti_expenses (
77
+ id SERIAL PRIMARY KEY,
78
+ transaction_id VARCHAR(50) NOT NULL REFERENCES patti_transactions(id) ON DELETE CASCADE,
79
+ packing DECIMAL(10, 2) DEFAULT 0,
80
+ godown DECIMAL(10, 2) DEFAULT 0,
81
+ hamali DECIMAL(10, 2) DEFAULT 0,
82
+ commission DECIMAL(10, 2) DEFAULT 0,
83
+ gaadi_bhade DECIMAL(10, 2) DEFAULT 0,
84
+ advance DECIMAL(10, 2) DEFAULT 0,
85
+ other_expenses DECIMAL(10, 2) DEFAULT 0,
86
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
87
+ )
88
+ `);
89
+
90
+ await client.query(`
91
+ CREATE TABLE IF NOT EXISTS patti_payments (
92
+ id SERIAL PRIMARY KEY,
93
+ transaction_id VARCHAR(50) NOT NULL REFERENCES patti_transactions(id) ON DELETE CASCADE,
94
+ mode VARCHAR(20) NOT NULL CHECK (mode IN ('cash', 'online', 'cheque', 'due')),
95
+ amount DECIMAL(12, 2) NOT NULL,
96
+ reference VARCHAR(255),
97
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
98
+ )
99
+ `);
100
+
101
+ await client.query(`
102
+ CREATE INDEX IF NOT EXISTS idx_patti_transactions_party ON patti_transactions(party_id);
103
+ CREATE INDEX IF NOT EXISTS idx_patti_transactions_date ON patti_transactions(bill_date);
104
+ CREATE INDEX IF NOT EXISTS idx_patti_transaction_items_tx ON patti_transaction_items(transaction_id);
105
+ CREATE INDEX IF NOT EXISTS idx_patti_lots_mirchi ON patti_lots(mirchi_type_id);
106
+ CREATE INDEX IF NOT EXISTS idx_patti_lots_status ON patti_lots(status);
107
+ `);
108
+
109
+ await client.query('COMMIT');
110
+ console.log('✅ migrate5 completed successfully!');
111
+ } catch (error) {
112
+ await client.query('ROLLBACK');
113
+ console.error('❌ migrate5 failed:', error);
114
+ throw error;
115
+ } finally {
116
+ client.release();
117
+ await pool.end();
118
+ }
119
+ };
120
+
121
+ migrate5().catch(err => {
122
+ console.error('Fatal error:', err);
123
+ process.exit(1);
124
+ });
src/db/undo_migrate5.js ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const pool = require('./config');
2
+
3
+ const undoMigrate5 = async () => {
4
+ const client = await pool.connect();
5
+
6
+ try {
7
+ console.log('🧹 Rolling back database migration (undo_migrate5)...');
8
+
9
+ await client.query('BEGIN');
10
+
11
+ await client.query('DROP TABLE IF EXISTS patti_payments CASCADE');
12
+ await client.query('DROP TABLE IF EXISTS patti_expenses CASCADE');
13
+ await client.query('DROP TABLE IF EXISTS patti_transaction_items CASCADE');
14
+ await client.query('DROP TABLE IF EXISTS patti_transactions CASCADE');
15
+ await client.query('DROP TABLE IF EXISTS patti_lots CASCADE');
16
+
17
+ await client.query('COMMIT');
18
+ console.log('✅ undo_migrate5 completed successfully!');
19
+ } catch (error) {
20
+ await client.query('ROLLBACK');
21
+ console.error('❌ undo_migrate5 failed:', error);
22
+ throw error;
23
+ } finally {
24
+ client.release();
25
+ await pool.end();
26
+ }
27
+ };
28
+
29
+ undoMigrate5().catch(err => {
30
+ console.error('Fatal error:', err);
31
+ process.exit(1);
32
+ });
src/routes/jama.js ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const pool = require('../db/config');
4
+ const { v4: uuidv4 } = require('uuid');
5
+
6
+ // GET jama entries for a party
7
+ router.get('/:partyId', async (req, res) => {
8
+ try {
9
+ const { partyId } = req.params;
10
+ const result = await pool.query(
11
+ `SELECT id, party_id, entry_date, amount, note, created_at
12
+ FROM party_jama_entries
13
+ WHERE party_id = $1
14
+ ORDER BY entry_date ASC, created_at ASC`,
15
+ [partyId]
16
+ );
17
+ res.json({ success: true, data: result.rows });
18
+ } catch (error) {
19
+ console.error('Error fetching jama entries:', error);
20
+ res.status(500).json({ success: false, message: error.message });
21
+ }
22
+ });
23
+
24
+ // POST create jama entry for a party (overall receipt)
25
+ router.post('/:partyId', async (req, res) => {
26
+ const client = await pool.connect();
27
+
28
+ try {
29
+ const { partyId } = req.params;
30
+ const { amount, entry_date, note } = req.body;
31
+
32
+ const parsed = typeof amount === 'number' ? amount : parseFloat(String(amount));
33
+ if (!Number.isFinite(parsed) || parsed <= 0) {
34
+ return res.status(400).json({ success: false, message: 'Amount must be greater than 0' });
35
+ }
36
+
37
+ await client.query('BEGIN');
38
+
39
+ // ensure party exists
40
+ const partyRes = await client.query('SELECT id, current_balance FROM parties WHERE id = $1', [partyId]);
41
+ if (partyRes.rows.length === 0) {
42
+ await client.query('ROLLBACK');
43
+ return res.status(404).json({ success: false, message: 'Party not found' });
44
+ }
45
+
46
+ const id = uuidv4();
47
+ const date = entry_date || new Date().toISOString().split('T')[0];
48
+
49
+ const insertRes = await client.query(
50
+ `INSERT INTO party_jama_entries (id, party_id, entry_date, amount, note)
51
+ VALUES ($1, $2, $3, $4, $5)
52
+ RETURNING id, party_id, entry_date, amount, note, created_at`,
53
+ [id, partyId, date, parsed, note || null]
54
+ );
55
+
56
+ // For JAWAAK (sales) parties, jama reduces receivable -> decrease current_balance
57
+ await client.query(
58
+ 'UPDATE parties SET current_balance = current_balance - $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2',
59
+ [parsed, partyId]
60
+ );
61
+
62
+ await client.query('COMMIT');
63
+
64
+ res.json({
65
+ success: true,
66
+ data: insertRes.rows[0],
67
+ message: 'Jama saved successfully'
68
+ });
69
+
70
+ } catch (error) {
71
+ await client.query('ROLLBACK');
72
+ console.error('Error creating jama entry:', error);
73
+ res.status(500).json({ success: false, message: error.message });
74
+ } finally {
75
+ client.release();
76
+ }
77
+ });
78
+
79
+ // DELETE revert jama entry
80
+ router.delete('/:partyId/:entryId', async (req, res) => {
81
+ const client = await pool.connect();
82
+
83
+ try {
84
+ const { partyId, entryId } = req.params;
85
+
86
+ await client.query('BEGIN');
87
+
88
+ const entryRes = await client.query(
89
+ 'SELECT id, party_id, amount FROM party_jama_entries WHERE id = $1 AND party_id = $2',
90
+ [entryId, partyId]
91
+ );
92
+
93
+ if (entryRes.rows.length === 0) {
94
+ await client.query('ROLLBACK');
95
+ return res.status(404).json({ success: false, message: 'Jama entry not found' });
96
+ }
97
+
98
+ const amtRaw = entryRes.rows[0].amount;
99
+ const amt = typeof amtRaw === 'number' ? amtRaw : parseFloat(String(amtRaw)) || 0;
100
+
101
+ await client.query('DELETE FROM party_jama_entries WHERE id = $1 AND party_id = $2', [entryId, partyId]);
102
+
103
+ await client.query(
104
+ 'UPDATE parties SET current_balance = current_balance + $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2',
105
+ [amt, partyId]
106
+ );
107
+
108
+ await client.query('COMMIT');
109
+
110
+ res.json({ success: true, message: 'Jama reverted successfully' });
111
+ } catch (error) {
112
+ await client.query('ROLLBACK');
113
+ console.error('Error reverting jama entry:', error);
114
+ res.status(500).json({ success: false, message: error.message });
115
+ } finally {
116
+ client.release();
117
+ }
118
+ });
119
+
120
+ module.exports = router;
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/patti.js ADDED
@@ -0,0 +1,446 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const pool = require('../db/config');
4
+ const { v4: uuidv4 } = require('uuid');
5
+
6
+ const fetchPattiTransaction = async (client, transactionId) => {
7
+ const result = await client.query(`
8
+ SELECT t.*,
9
+ json_agg(DISTINCT jsonb_build_object(
10
+ 'id', ti.id,
11
+ 'mirchi_type_id', ti.mirchi_type_id,
12
+ 'mirchi_name', ti.mirchi_name,
13
+ 'quality', ti.quality,
14
+ 'lot_id', ti.lot_id,
15
+ 'lot_number', ti.lot_number,
16
+ 'poti_weights', ti.poti_weights,
17
+ 'gross_weight', ti.gross_weight,
18
+ 'poti_count', ti.poti_count,
19
+ 'total_potya', ti.total_potya,
20
+ 'net_weight', ti.net_weight,
21
+ 'rate_per_kg', ti.rate_per_kg,
22
+ 'item_total', ti.item_total
23
+ )) FILTER (WHERE ti.id IS NOT NULL) as items,
24
+ json_build_object(
25
+ 'packing', e.packing,
26
+ 'godown', e.godown,
27
+ 'hamali', e.hamali,
28
+ 'commission', e.commission,
29
+ 'gaadi_bhade', e.gaadi_bhade,
30
+ 'advance', e.advance,
31
+ 'other_expenses', e.other_expenses
32
+ ) as expenses,
33
+ json_agg(DISTINCT jsonb_build_object(
34
+ 'mode', p.mode,
35
+ 'amount', p.amount,
36
+ 'reference', p.reference
37
+ )) FILTER (WHERE p.id IS NOT NULL) as payments
38
+ FROM patti_transactions t
39
+ LEFT JOIN patti_transaction_items ti ON t.id = ti.transaction_id
40
+ LEFT JOIN patti_expenses e ON t.id = e.transaction_id
41
+ LEFT JOIN patti_payments p ON t.id = p.transaction_id
42
+ WHERE t.id = $1
43
+ GROUP BY t.id, e.id
44
+ `, [transactionId]);
45
+
46
+ return result.rows[0];
47
+ };
48
+
49
+ // GET patti transactions
50
+ router.get('/transactions', async (req, res) => {
51
+ try {
52
+ const { party_id, bill_type, is_return, invoice_group_id } = req.query;
53
+
54
+ let query = `
55
+ SELECT t.*,
56
+ json_agg(DISTINCT jsonb_build_object(
57
+ 'id', ti.id,
58
+ 'mirchi_type_id', ti.mirchi_type_id,
59
+ 'mirchi_name', ti.mirchi_name,
60
+ 'quality', ti.quality,
61
+ 'lot_id', ti.lot_id,
62
+ 'lot_number', ti.lot_number,
63
+ 'poti_weights', ti.poti_weights,
64
+ 'gross_weight', ti.gross_weight,
65
+ 'poti_count', ti.poti_count,
66
+ 'total_potya', ti.total_potya,
67
+ 'net_weight', ti.net_weight,
68
+ 'rate_per_kg', ti.rate_per_kg,
69
+ 'item_total', ti.item_total
70
+ )) FILTER (WHERE ti.id IS NOT NULL) as items,
71
+ json_build_object(
72
+ 'packing', e.packing,
73
+ 'godown', e.godown,
74
+ 'hamali', e.hamali,
75
+ 'commission', e.commission,
76
+ 'gaadi_bhade', e.gaadi_bhade,
77
+ 'advance', e.advance,
78
+ 'other_expenses', e.other_expenses
79
+ ) as expenses,
80
+ json_agg(DISTINCT jsonb_build_object(
81
+ 'mode', p.mode,
82
+ 'amount', p.amount,
83
+ 'reference', p.reference
84
+ )) FILTER (WHERE p.id IS NOT NULL) as payments
85
+ FROM patti_transactions t
86
+ LEFT JOIN patti_transaction_items ti ON t.id = ti.transaction_id
87
+ LEFT JOIN patti_expenses e ON t.id = e.transaction_id
88
+ LEFT JOIN patti_payments p ON t.id = p.transaction_id
89
+ `;
90
+
91
+ const conditions = [];
92
+ const params = [];
93
+ let paramCount = 1;
94
+
95
+ if (party_id) {
96
+ conditions.push(`t.party_id = $${paramCount}`);
97
+ params.push(party_id);
98
+ paramCount++;
99
+ }
100
+
101
+ if (bill_type) {
102
+ conditions.push(`t.bill_type = $${paramCount}`);
103
+ params.push(bill_type);
104
+ paramCount++;
105
+ }
106
+
107
+ if (is_return !== undefined) {
108
+ conditions.push(`t.is_return = $${paramCount}`);
109
+ params.push(is_return === 'true');
110
+ paramCount++;
111
+ }
112
+
113
+ if (invoice_group_id) {
114
+ conditions.push(`t.invoice_group_id = $${paramCount}`);
115
+ params.push(invoice_group_id);
116
+ paramCount++;
117
+ }
118
+
119
+ if (conditions.length > 0) {
120
+ query += ' WHERE ' + conditions.join(' AND ');
121
+ }
122
+
123
+ query += ' GROUP BY t.id, e.id ORDER BY t.bill_date DESC, t.created_at DESC';
124
+
125
+ const result = await pool.query(query, params);
126
+ res.json(result.rows);
127
+ } catch (error) {
128
+ console.error('Error fetching patti transactions:', error);
129
+ res.status(500).json({ success: false, message: error.message });
130
+ }
131
+ });
132
+
133
+ // GET patti active lots
134
+ router.get('/lots/active', async (req, res) => {
135
+ try {
136
+ const result = await pool.query(
137
+ `SELECT * FROM patti_lots
138
+ WHERE status = 'active' AND remaining_quantity > 0
139
+ ORDER BY purchase_date DESC`
140
+ );
141
+ res.json(result.rows);
142
+ } catch (error) {
143
+ console.error('Error fetching patti lots:', error);
144
+ res.status(500).json({ success: false, message: error.message });
145
+ }
146
+ });
147
+
148
+ // GET patti available lots by mirchi type
149
+ router.get('/lots/available/:mirchiTypeId', async (req, res) => {
150
+ try {
151
+ const { mirchiTypeId } = req.params;
152
+ const result = await pool.query(
153
+ `SELECT * FROM patti_lots
154
+ WHERE mirchi_type_id = $1
155
+ AND status = 'active'
156
+ AND remaining_quantity > 0
157
+ ORDER BY purchase_date DESC`,
158
+ [mirchiTypeId]
159
+ );
160
+ res.json(result.rows);
161
+ } catch (error) {
162
+ console.error('Error fetching patti available lots:', error);
163
+ res.status(500).json({ success: false, message: error.message });
164
+ }
165
+ });
166
+
167
+ const insertPattiTransaction = async (client, payload, billType) => {
168
+ const {
169
+ id,
170
+ bill_number,
171
+ bill_date,
172
+ is_return,
173
+ party_id,
174
+ party_name,
175
+ invoice_group_id,
176
+ items,
177
+ expenses,
178
+ payments,
179
+ gross_weight_total,
180
+ net_weight_total,
181
+ subtotal,
182
+ total_expenses,
183
+ total_amount,
184
+ paid_amount,
185
+ balance_amount,
186
+ } = payload;
187
+
188
+ if (!party_id) {
189
+ return { error: 'Party is required' };
190
+ }
191
+
192
+ if (!items || items.length === 0) {
193
+ return { error: 'At least one item is required' };
194
+ }
195
+
196
+ const transactionId = id || uuidv4();
197
+
198
+ await client.query(
199
+ `INSERT INTO patti_transactions (
200
+ id, bill_number, bill_date, bill_type, is_return, party_id, party_name, invoice_group_id,
201
+ gross_weight_total, net_weight_total, subtotal, total_expenses,
202
+ total_amount, paid_amount, balance_amount
203
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15)
204
+ RETURNING *`,
205
+ [
206
+ transactionId,
207
+ bill_number,
208
+ bill_date,
209
+ billType,
210
+ is_return || false,
211
+ party_id,
212
+ party_name,
213
+ invoice_group_id || null,
214
+ gross_weight_total,
215
+ net_weight_total,
216
+ subtotal,
217
+ total_expenses,
218
+ total_amount,
219
+ paid_amount,
220
+ balance_amount,
221
+ ]
222
+ );
223
+
224
+ for (const item of items) {
225
+ const itemId = item.id || uuidv4();
226
+ await client.query(
227
+ `INSERT INTO patti_transaction_items (
228
+ id, transaction_id, mirchi_type_id, mirchi_name, quality, lot_id,
229
+ lot_number, poti_weights, gross_weight, poti_count, total_potya, net_weight,
230
+ rate_per_kg, item_total
231
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14)`,
232
+ [
233
+ itemId,
234
+ transactionId,
235
+ item.mirchi_type_id,
236
+ item.mirchi_name,
237
+ item.quality || null,
238
+ item.lot_id || null,
239
+ item.lot_number || null,
240
+ JSON.stringify(item.poti_weights || []),
241
+ item.gross_weight,
242
+ item.poti_count,
243
+ item.total_potya || 0,
244
+ item.net_weight,
245
+ item.rate_per_kg,
246
+ item.item_total,
247
+ ]
248
+ );
249
+ }
250
+
251
+ if (expenses) {
252
+ await client.query(
253
+ `INSERT INTO patti_expenses (
254
+ transaction_id, packing, godown, hamali, commission, gaadi_bhade, advance, other_expenses
255
+ ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8)`,
256
+ [
257
+ transactionId,
258
+ expenses.packing || 0,
259
+ expenses.godown || 0,
260
+ expenses.hamali || 0,
261
+ expenses.commission || 0,
262
+ expenses.gaadi_bhade || 0,
263
+ expenses.advance || 0,
264
+ expenses.other_expenses || 0,
265
+ ]
266
+ );
267
+ }
268
+
269
+ if (payments && payments.length > 0) {
270
+ for (const payment of payments) {
271
+ if (payment.amount > 0) {
272
+ await client.query(
273
+ `INSERT INTO patti_payments (transaction_id, mode, amount, reference)
274
+ VALUES ($1, $2, $3, $4)`,
275
+ [transactionId, payment.mode, payment.amount, payment.reference]
276
+ );
277
+ }
278
+ }
279
+ }
280
+
281
+ return { transactionId };
282
+ };
283
+
284
+ const upsertPattiLotForAwaak = async (client, item, billDate) => {
285
+ const mirchi = await client.query('SELECT name FROM mirchi_types WHERE id = $1', [item.mirchi_type_id]);
286
+ const mirchiName = mirchi.rows[0]?.name || 'Unknown';
287
+ const lotNumber = item.lot_number;
288
+
289
+ if (!lotNumber) {
290
+ throw new Error('LOT number is required for Patti Awaak');
291
+ }
292
+
293
+ const existingLot = await client.query(
294
+ 'SELECT id FROM patti_lots WHERE lot_number = $1',
295
+ [lotNumber]
296
+ );
297
+
298
+ if (existingLot.rows.length > 0) {
299
+ const lotId = existingLot.rows[0].id;
300
+ await client.query(
301
+ `UPDATE patti_lots
302
+ SET total_quantity = total_quantity + $1,
303
+ remaining_quantity = remaining_quantity + $1,
304
+ status = 'active',
305
+ updated_at = CURRENT_TIMESTAMP
306
+ WHERE id = $2`,
307
+ [item.net_weight, lotId]
308
+ );
309
+
310
+ return lotId;
311
+ }
312
+
313
+ const lotId = uuidv4();
314
+ await client.query(
315
+ `INSERT INTO patti_lots (id, lot_number, mirchi_type_id, mirchi_name, total_quantity, remaining_quantity, purchase_date, status, avg_rate, owner_party_id)
316
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
317
+ [
318
+ lotId,
319
+ lotNumber,
320
+ item.mirchi_type_id,
321
+ mirchiName,
322
+ item.net_weight,
323
+ item.net_weight,
324
+ billDate,
325
+ 'active',
326
+ item.rate_per_kg,
327
+ item.owner_party_id || null
328
+ ]
329
+ );
330
+
331
+ return lotId;
332
+ };
333
+
334
+ const applyPattiAwaakStock = async (client, items, isReturn) => {
335
+ if (!items || items.length === 0) return;
336
+
337
+ for (const item of items) {
338
+ if (!item.lot_id) continue;
339
+
340
+ if (isReturn) {
341
+ await client.query(
342
+ `UPDATE patti_lots
343
+ SET total_quantity = total_quantity - $1,
344
+ remaining_quantity = remaining_quantity - $1,
345
+ status = CASE WHEN remaining_quantity - $1 <= 0 THEN 'sold_out' ELSE 'active' END,
346
+ updated_at = CURRENT_TIMESTAMP
347
+ WHERE id = $2`,
348
+ [item.net_weight, item.lot_id]
349
+ );
350
+ }
351
+ }
352
+ };
353
+
354
+ const applyPattiJawaakStock = async (client, items, isReturn) => {
355
+ if (!items || items.length === 0) return;
356
+
357
+ for (const item of items) {
358
+ if (!item.lot_id) continue;
359
+
360
+ const delta = isReturn ? item.gross_weight : -item.gross_weight;
361
+ await client.query(
362
+ `UPDATE patti_lots
363
+ SET remaining_quantity = remaining_quantity + $1,
364
+ status = CASE WHEN remaining_quantity + $1 <= 0 THEN 'sold_out' ELSE 'active' END,
365
+ updated_at = CURRENT_TIMESTAMP
366
+ WHERE id = $2`,
367
+ [delta, item.lot_id]
368
+ );
369
+ }
370
+ };
371
+
372
+ // POST create patti awaak
373
+ router.post('/awaak', async (req, res) => {
374
+ const client = await pool.connect();
375
+
376
+ try {
377
+ const payload = req.body;
378
+ const normalizedItems = [...(payload.items || [])];
379
+
380
+ if (!payload.is_return) {
381
+ for (const item of normalizedItems) {
382
+ const lotId = await upsertPattiLotForAwaak(client, item, payload.bill_date);
383
+ item.lot_id = lotId;
384
+ }
385
+ }
386
+
387
+ await client.query('BEGIN');
388
+
389
+ const insertResult = await insertPattiTransaction(
390
+ client,
391
+ { ...payload, items: normalizedItems },
392
+ 'patti_awaak'
393
+ );
394
+ if (insertResult.error) {
395
+ await client.query('ROLLBACK');
396
+ return res.status(400).json({ success: false, message: insertResult.error });
397
+ }
398
+
399
+ // Patti Awaak return reduces stock (net). Incoming handled in upsert above.
400
+ await applyPattiAwaakStock(client, normalizedItems, payload.is_return);
401
+
402
+ await client.query('COMMIT');
403
+
404
+ const completeTransaction = await fetchPattiTransaction(client, insertResult.transactionId);
405
+ res.json({ success: true, data: completeTransaction, message: 'Patti Awaak saved successfully' });
406
+ } catch (error) {
407
+ await client.query('ROLLBACK');
408
+ console.error('Error saving patti awaak:', error);
409
+ res.status(500).json({ success: false, message: error.message });
410
+ } finally {
411
+ client.release();
412
+ }
413
+ });
414
+
415
+ // POST create patti jawaak
416
+ router.post('/jawaak', async (req, res) => {
417
+ const client = await pool.connect();
418
+
419
+ try {
420
+ const payload = req.body;
421
+
422
+ await client.query('BEGIN');
423
+
424
+ const insertResult = await insertPattiTransaction(client, payload, 'patti_jawaak');
425
+ if (insertResult.error) {
426
+ await client.query('ROLLBACK');
427
+ return res.status(400).json({ success: false, message: insertResult.error });
428
+ }
429
+
430
+ // Patti Jawaak reduces stock (gross) or adds back on return
431
+ await applyPattiJawaakStock(client, payload.items, payload.is_return);
432
+
433
+ await client.query('COMMIT');
434
+
435
+ const completeTransaction = await fetchPattiTransaction(client, insertResult.transactionId);
436
+ res.json({ success: true, data: completeTransaction, message: 'Patti Jawaak saved successfully' });
437
+ } catch (error) {
438
+ await client.query('ROLLBACK');
439
+ console.error('Error saving patti jawaak:', error);
440
+ res.status(500).json({ success: false, message: error.message });
441
+ } finally {
442
+ client.release();
443
+ }
444
+ });
445
+
446
+ module.exports = router;
src/routes/transactions.js CHANGED
@@ -161,6 +161,7 @@ router.post('/', async (req, res) => {
161
  is_return,
162
  party_id,
163
  party_name,
 
164
  items,
165
  expenses,
166
  payments,
@@ -194,14 +195,14 @@ router.post('/', async (req, res) => {
194
  // 1. Insert transaction
195
  const txResult = await client.query(
196
  `INSERT INTO transactions (
197
- id, bill_number, bill_date, bill_type, is_return, party_id, party_name,
198
  gross_weight_total, net_weight_total, subtotal, total_expenses,
199
  total_amount, paid_amount, balance_amount
200
- ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
201
  RETURNING *`,
202
  [
203
  transactionId, bill_number, bill_date, bill_type, is_return || false,
204
- party_id, party_name, gross_weight_total, net_weight_total,
205
  subtotal, total_expenses, total_amount, paid_amount, balance_amount
206
  ]
207
  );
@@ -260,33 +261,16 @@ router.post('/', async (req, res) => {
260
  }
261
 
262
  // 5. Update party balance
263
- if (bill_type === 'jawaak') {
264
- // Purchase: We owe them (decrease balance)
265
- if (is_return) {
266
- await client.query(
267
- 'UPDATE parties SET current_balance = current_balance + $1 WHERE id = $2',
268
- [balance_amount, party_id]
269
- );
270
- } else {
271
- await client.query(
272
- 'UPDATE parties SET current_balance = current_balance - $1 WHERE id = $2',
273
- [balance_amount, party_id]
274
- );
275
- }
276
- } else {
277
- // Sales: They owe us (increase balance)
278
- if (is_return) {
279
- await client.query(
280
- 'UPDATE parties SET current_balance = current_balance - $1 WHERE id = $2',
281
- [balance_amount, party_id]
282
- );
283
- } else {
284
- await client.query(
285
- 'UPDATE parties SET current_balance = current_balance + $1 WHERE id = $2',
286
- [balance_amount, party_id]
287
- );
288
- }
289
- }
290
 
291
  // 6. Update lots (stock management)
292
  // AWAAK = Purchase (Stock IN), JAWAAK = Sale (Stock OUT)
@@ -530,4 +514,164 @@ router.patch('/:id/payment', async (req, res) => {
530
  }
531
  });
532
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
533
  module.exports = router;
 
161
  is_return,
162
  party_id,
163
  party_name,
164
+ invoice_group_id,
165
  items,
166
  expenses,
167
  payments,
 
195
  // 1. Insert transaction
196
  const txResult = await client.query(
197
  `INSERT INTO transactions (
198
+ id, bill_number, bill_date, bill_type, is_return, party_id, party_name, invoice_group_id,
199
  gross_weight_total, net_weight_total, subtotal, total_expenses,
200
  total_amount, paid_amount, balance_amount
201
+ ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
202
  RETURNING *`,
203
  [
204
  transactionId, bill_number, bill_date, bill_type, is_return || false,
205
+ party_id, party_name, invoice_group_id || null, gross_weight_total, net_weight_total,
206
  subtotal, total_expenses, total_amount, paid_amount, balance_amount
207
  ]
208
  );
 
261
  }
262
 
263
  // 5. Update party balance
264
+ // Convention: +ve = they owe us (receivable), -ve = we owe them (payable)
265
+ // JAWAAK (sales): increases receivable (+). AWAAK (purchase): increases payable (-).
266
+ const sign = bill_type === 'jawaak' ? 1 : -1;
267
+ const dir = is_return ? -1 : 1;
268
+ const balanceDelta = sign * dir * balance_amount;
269
+
270
+ await client.query(
271
+ 'UPDATE parties SET current_balance = current_balance + $1 WHERE id = $2',
272
+ [balanceDelta, party_id]
273
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
 
275
  // 6. Update lots (stock management)
276
  // AWAAK = Purchase (Stock IN), JAWAAK = Sale (Stock OUT)
 
514
  }
515
  });
516
 
517
+ // POST revert transaction (rollback stock + balances, then delete)
518
+ router.post('/:id/revert', async (req, res) => {
519
+ const client = await pool.connect();
520
+
521
+ try {
522
+ const { id } = req.params;
523
+
524
+ await client.query('BEGIN');
525
+
526
+ const txResult = await client.query(
527
+ 'SELECT * FROM transactions WHERE id = $1',
528
+ [id]
529
+ );
530
+
531
+ if (txResult.rows.length === 0) {
532
+ await client.query('ROLLBACK');
533
+ return res.status(404).json({ success: false, message: 'Transaction not found' });
534
+ }
535
+
536
+ const tx = txResult.rows[0];
537
+
538
+ const itemsResult = await client.query(
539
+ 'SELECT * FROM transaction_items WHERE transaction_id = $1',
540
+ [id]
541
+ );
542
+
543
+ const items = itemsResult.rows;
544
+
545
+ if (!items || items.length === 0) {
546
+ await client.query('ROLLBACK');
547
+ return res.status(400).json({ success: false, message: 'No items found for this transaction' });
548
+ }
549
+
550
+ // Rollback lots (inverse of creation logic)
551
+ for (const item of items) {
552
+ if (!item.lot_id) continue;
553
+
554
+ const lotResult = await client.query(
555
+ 'SELECT * FROM lots WHERE id = $1',
556
+ [item.lot_id]
557
+ );
558
+
559
+ if (lotResult.rows.length === 0) {
560
+ await client.query('ROLLBACK');
561
+ return res.status(400).json({ success: false, message: `Lot not found for item ${item.id}` });
562
+ }
563
+
564
+ const lot = lotResult.rows[0];
565
+ const netWeight = Number(item.net_weight || 0);
566
+ const grossWeight = Number(item.gross_weight || 0);
567
+
568
+ // AWAAK = Purchase (Stock IN), JAWAAK = Sale (Stock OUT)
569
+ if (tx.bill_type === 'awaak' && !tx.is_return) {
570
+ // Reverting a purchase: subtract stock that was added
571
+ if (Number(lot.remaining_quantity) < netWeight || Number(lot.total_quantity) < netWeight) {
572
+ await client.query('ROLLBACK');
573
+ return res.status(409).json({
574
+ success: false,
575
+ message: `Cannot revert: stock already used for lot ${lot.lot_number}. Remaining ${lot.remaining_quantity}, required ${netWeight}`
576
+ });
577
+ }
578
+
579
+ await client.query(
580
+ `UPDATE lots
581
+ SET total_quantity = total_quantity - $1,
582
+ remaining_quantity = remaining_quantity - $1,
583
+ status = CASE WHEN remaining_quantity - $1 <= 0 THEN 'sold_out' ELSE 'active' END,
584
+ updated_at = CURRENT_TIMESTAMP
585
+ WHERE id = $2`,
586
+ [netWeight, item.lot_id]
587
+ );
588
+ } else if (tx.bill_type === 'awaak' && tx.is_return) {
589
+ // Reverting a purchase return: add stock back
590
+ await client.query(
591
+ `UPDATE lots
592
+ SET total_quantity = total_quantity + $1,
593
+ remaining_quantity = remaining_quantity + $1,
594
+ status = 'active',
595
+ updated_at = CURRENT_TIMESTAMP
596
+ WHERE id = $2`,
597
+ [netWeight, item.lot_id]
598
+ );
599
+ } else if (tx.bill_type === 'jawaak' && !tx.is_return) {
600
+ // Reverting a sale: add stock back to remaining (gross)
601
+ await client.query(
602
+ `UPDATE lots
603
+ SET remaining_quantity = remaining_quantity + $1,
604
+ status = CASE WHEN remaining_quantity + $1 > 0 THEN 'active' ELSE status END,
605
+ updated_at = CURRENT_TIMESTAMP
606
+ WHERE id = $2`,
607
+ [grossWeight, item.lot_id]
608
+ );
609
+ } else if (tx.bill_type === 'jawaak' && tx.is_return) {
610
+ // Reverting a sales return: subtract stock that was added back (gross)
611
+ if (Number(lot.remaining_quantity) < grossWeight) {
612
+ await client.query('ROLLBACK');
613
+ return res.status(409).json({
614
+ success: false,
615
+ message: `Cannot revert: not enough remaining stock in lot ${lot.lot_number}. Remaining ${lot.remaining_quantity}, required ${grossWeight}`
616
+ });
617
+ }
618
+
619
+ await client.query(
620
+ `UPDATE lots
621
+ SET remaining_quantity = remaining_quantity - $1,
622
+ status = CASE WHEN remaining_quantity - $1 <= 0 THEN 'sold_out' ELSE 'active' END,
623
+ updated_at = CURRENT_TIMESTAMP
624
+ WHERE id = $2`,
625
+ [grossWeight, item.lot_id]
626
+ );
627
+ }
628
+ }
629
+
630
+ // Rollback party balance (inverse of creation logic in this file)
631
+ const balanceAmount = Number(tx.balance_amount || 0);
632
+
633
+ if (tx.bill_type === 'jawaak') {
634
+ if (tx.is_return) {
635
+ await client.query(
636
+ 'UPDATE parties SET current_balance = current_balance - $1 WHERE id = $2',
637
+ [balanceAmount, tx.party_id]
638
+ );
639
+ } else {
640
+ await client.query(
641
+ 'UPDATE parties SET current_balance = current_balance + $1 WHERE id = $2',
642
+ [balanceAmount, tx.party_id]
643
+ );
644
+ }
645
+ } else {
646
+ if (tx.is_return) {
647
+ await client.query(
648
+ 'UPDATE parties SET current_balance = current_balance + $1 WHERE id = $2',
649
+ [balanceAmount, tx.party_id]
650
+ );
651
+ } else {
652
+ await client.query(
653
+ 'UPDATE parties SET current_balance = current_balance - $1 WHERE id = $2',
654
+ [balanceAmount, tx.party_id]
655
+ );
656
+ }
657
+ }
658
+
659
+ // Delete transaction (items/expenses/payments will cascade delete)
660
+ await client.query('DELETE FROM transactions WHERE id = $1', [id]);
661
+
662
+ await client.query('COMMIT');
663
+
664
+ res.json({
665
+ success: true,
666
+ message: 'Transaction reverted successfully'
667
+ });
668
+ } catch (error) {
669
+ await client.query('ROLLBACK');
670
+ console.error('Error reverting transaction:', error);
671
+ res.status(500).json({ success: false, message: error.message });
672
+ } finally {
673
+ client.release();
674
+ }
675
+ });
676
+
677
  module.exports = router;
src/server.js CHANGED
@@ -6,6 +6,8 @@ const partiesRouter = require('./routes/parties');
6
  const mirchiTypesRouter = require('./routes/mirchiTypes');
7
  const lotsRouter = require('./routes/lots');
8
  const transactionsRouter = require('./routes/transactions');
 
 
9
 
10
  const app = express();
11
  const PORT = process.env.PORT || 4000;
@@ -26,6 +28,8 @@ app.use('/api/parties', partiesRouter);
26
  app.use('/api/mirchi-types', mirchiTypesRouter);
27
  app.use('/api/lots', lotsRouter);
28
  app.use('/api/transactions', transactionsRouter);
 
 
29
 
30
  // Health check
31
  app.get('/health', (req, res) => {
 
6
  const mirchiTypesRouter = require('./routes/mirchiTypes');
7
  const lotsRouter = require('./routes/lots');
8
  const transactionsRouter = require('./routes/transactions');
9
+ const jamaRouter = require('./routes/jama');
10
+ const pattiRouter = require('./routes/patti');
11
 
12
  const app = express();
13
  const PORT = process.env.PORT || 4000;
 
28
  app.use('/api/mirchi-types', mirchiTypesRouter);
29
  app.use('/api/lots', lotsRouter);
30
  app.use('/api/transactions', transactionsRouter);
31
+ app.use('/api/jama', jamaRouter);
32
+ app.use('/api/patti', pattiRouter);
33
 
34
  // Health check
35
  app.get('/health', (req, res) => {