Guest
Am Frontend besiedelt der Select2 die Felder nicht, damit der Benutzer auswählen kann
Post
by Guest » 08 Feb 2025, 16:36
Ich habe eine Rechnung, um das Datum für die PostgreSQL -Datenbank zu sammeln, und ich habe Probleme, mit Endpunkten ordnungsgemäß zu arbeiten und die Dropdown -Liste für das Feld des Verkaufsnamens und des Produktnamens zu bevölkern. JSON -Antwort und es liefert das Frontend mit Sätzen, während mit meinem anderen Backend -Code die JSON -Antworten gültig waren, die Select2 konnte nicht ausgefüllt werden und dann den geteilten Namen im Feld beibehalten. < /p>
Frontend < /p>
Code: Select all
// Initialize Select2 components
$(document).ready(function() {
$('#salesman').select2({
placeholder: 'Select Salesman',
ajax: {
url: '/api/salesmen',
dataType: 'json',
delay: 250,
processResults: data => ({
results: data
})
}
});
initProductSelect($('.product-row').first().find('.product-name'));
});
function initProductSelect(element) {
element.select2({
placeholder: 'Select Product',
ajax: {
url: '/api/products',
dataType: 'json',
delay: 250,
processResults: data => ({
results: data
})
}
}).on('select2:select', async function(e) {
const row = $(this).closest('.product-row');
try {
const response = await fetch(`/api/product/${e.params.data.id}`);
const product = await response.json();
row.data('conversion', product.conversion_rate)
.find('.stock-ctn, .added-ctn, .return-ctn').attr('placeholder', product.primary_unit)
.closest('tr').find('.total-dispatch-ctn, .sold-ctn').text(`0 ${product.primary_unit}`);
} catch (error) {
console.error('Error loading product:', error);
}
});
}
function convertToPieces(ctn, pcs, row) {
return (ctn * row.data('conversion')) + (parseInt(pcs) || 0);
}
function updateTotalDispatch(row) {
const $row = $(row);
const conversion = $row.data('conversion') || 1;
const stockCtn = parseInt($row.find('.stock-ctn').val()) || 0;
const stockPcs = parseInt($row.find('.stock-pcs').val()) || 0;
const addedCtn = parseInt($row.find('.added-ctn').val()) || 0;
const addedPcs = parseInt($row.find('.added-pcs').val()) || 0;
const totalPieces = (stockCtn + addedCtn) * conversion + stockPcs + addedPcs;
$row.find('.total-dispatch-ctn').text(`${Math.floor(totalPieces/conversion)} ${$row.find('.stock-ctn').attr('placeholder')}`);
$row.find('.total-dispatch-pcs').text(`${totalPieces % conversion} Pcs`);
}
function updateSoldStock(row) {
const $row = $(row);
const conversion = $row.data('conversion') || 1;
// Parse inputs with fallback to 0 if empty or invalid
const stockCtn = parseInt($row.find('.stock-ctn').val()) || 0;
const stockPcs = parseInt($row.find('.stock-pcs').val()) || 0;
const addedCtn = parseInt($row.find('.added-ctn').val()) || 0;
const addedPcs = parseInt($row.find('.added-pcs').val()) || 0;
const returnCtn = parseInt($row.find('.return-ctn').val()) || 0;
const returnPcs = parseInt($row.find('.return-pcs').val()) || 0;
// Calculate total stock and return
const totalStock = (stockCtn + addedCtn) * conversion + (stockPcs + addedPcs);
const totalReturn = returnCtn * conversion + returnPcs;
// Validate return doesn't exceed stock
if (totalReturn > totalStock) {
const maxReturn = totalStock;
$row.find('.return-ctn').val(Math.floor(maxReturn / conversion));
$row.find('.return-pcs').val(maxReturn % conversion);
alert("Return exceeds available stock!");
return;
}
// Calculate sold stock
const soldPcs = totalStock - totalReturn;
const soldCtn = Math.floor(soldPcs / conversion);
const soldRemainingPcs = soldPcs % conversion;
// Update display
$row.find('.sold-ctn').text(`${soldCtn} ${$row.find('.stock-ctn').attr('placeholder')}`);
$row.find('.sold-pcs').text(`${soldRemainingPcs} Pcs`);
// Disable "Add Rate" button if all stock is allocated
let totalAllocated = 0;
$row.find('.rate-rows tr').each(function() {
const rateCtn = parseInt($(this).find('.rate-ctn').val()) || 0;
const ratePcs = parseInt($(this).find('.rate-pcs').val()) || 0;
totalAllocated += rateCtn * conversion + ratePcs;
});
$row.find('.add-rate').prop('disabled', totalAllocated >= soldPcs);
}
function addRate(button) {
const row = $(button).closest('.product-row');
const rateRows = row.find('.rate-rows');
const soldPcs = convertToPieces(
parseInt(row.find('.sold-ctn').text()),
parseInt(row.find('.sold-pcs').text()),
row
);
let totalAllocated = 0;
rateRows.find('tr').each(function() {
totalAllocated += convertToPieces(
parseInt($(this).find('.rate-ctn').val()),
parseInt($(this).find('.rate-pcs').val()),
row
);
});
if (totalAllocated >= soldPcs) {
alert("Allocated quantity exceeds sold stock!");
return;
}
const newRow = $('').html(`
-
${rateRows.children().length === 0 ?
'' : ''}
`);
rateRows.append(newRow);
updateSoldStock(row);
}
function removeRate(button) {
const row = $(button).closest('.product-row');
const rateRows = row.find('.rate-rows');
const lastRateRow = rateRows.find('tr').last();
if (lastRateRow.length > 0) {
lastRateRow.remove(); // Remove the last rate row
updateSoldStock(row); // Update sold stock after removal
} else {
alert("No rate rows to remove!");
}
}
function selectAllQuantity(checkbox) {
const row = $(checkbox).closest('.product-row');
const rateRow = $(checkbox).closest('tr');
const conversion = row.data('conversion');
const soldCtn = parseInt(row.find('.sold-ctn').text());
const soldPcs = parseInt(row.find('.sold-pcs').text());
if (checkbox.checked) {
rateRow.find('.rate-ctn').val(soldCtn).prop('disabled', true);
rateRow.find('.rate-pcs').val(soldPcs).prop('disabled', true);
} else {
rateRow.find('.rate-ctn, .rate-pcs').val('').prop('disabled', false);
}
}
function addProductRow() {
const newRow = $('.product-row').first().clone();
newRow.find('input').val('');
newRow.find('.select2-product').remove();
newRow.find('td:first').html('');
newRow.find('.serial-number').text($('.product-row').length + 1);
newRow.find('.rate-rows').empty();
newRow.find('.total-amount, .sold-ctn, .sold-pcs, .total-dispatch-ctn, .total-dispatch-pcs').text('0');
$('#products > tbody').append(newRow);
initProductSelect(newRow.find('.product-name'));
updateSerialNumbers();
}
function removeProductRow(button) {
const rows = $('.product-row');
if (rows.length === 1) {
rows.find('input').val('');
rows.find('.rate-rows').empty();
rows.find('.select2-product').val(null).trigger('change');
updateSoldStock(rows);
updateTotalDispatch(rows);
} else {
$(button).closest('.product-row').remove();
}
updateSerialNumbers();
}
function updateSerialNumbers() {
$('.product-row .serial-number').each((i, el) => $(el).text(i + 1));
}
function calculateTotal() {
let grandTotal = 0;
$('.product-row').each(function() {
let rowTotal = 0;
$(this).find('.rate-rows tr').each(function() {
const ctn = parseInt($(this).find('.rate-ctn').val()) || 0;
const pcs = parseInt($(this).find('.rate-pcs').val()) || 0;
const rate = parseFloat($(this).find('.rate-value').val()) || 0;
const conversion = $(this).closest('.product-row').data('conversion') || 1;
rowTotal += ((ctn * conversion) + pcs) * rate;
});
$(this).find('.total-amount').text(`रू ${rowTotal.toFixed(2)}`);
grandTotal += rowTotal;
});
$('#grand-total').text(`रू ${grandTotal.toFixed(2)}`);
}
async function saveChallan() {
const challanData = {
date: $('#date').val(),
miti: $('#challanMiti').val(),
challan_no: $('#challanNo').val(),
salesman_id: $('#salesman').val(),
items: [],
total_amount: parseFloat($('#grand-total').text().replace('रू ', ''))
};
$('.product-row').each(function() {
const $row = $(this);
const item = {
product_id: $row.find('.product-name').val(),
stock_van: {
ctn: parseInt($row.find('.stock-ctn').val()) || 0,
pcs: parseInt($row.find('.stock-pcs').val()) || 0
},
stock_added: {
ctn: parseInt($row.find('.added-ctn').val()) || 0,
pcs: parseInt($row.find('.added-pcs').val()) || 0
},
stock_return: {
ctn: parseInt($row.find('.return-ctn').val()) || 0,
pcs: parseInt($row.find('.return-pcs').val()) || 0
},
rates: []
};
$row.find('.rate-rows tr').each(function() {
item.rates.push({
ctn: parseInt($(this).find('.rate-ctn').val()) || 0,
pcs: parseInt($(this).find('.rate-pcs').val()) || 0,
rate: parseFloat($(this).find('.rate-value').val()) || 0
});
});
challanData.items.push(item);
});
try {
const response = await fetch('/api/challan', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(challanData)
});
const result = await response.json();
alert(result.message);
if (response.ok) cancelForm();
} catch (error) {
console.error('Error:', error);
alert('Error saving challan');
}
}
function cancelForm() {
$('#salesman').val(null).trigger('change');
$('#date, #challanMiti, #challanNo').val('');
$('.product-row').each(function(index) {
if (index > 0) $(this).remove();
else {
$(this).find('input').val('');
$(this).find('.rate-rows').empty();
$(this).find('.select2-product').val(null).trigger('change');
$(this).find('.total-amount, .sold-ctn, .sold-pcs, .total-dispatch-ctn, .total-dispatch-pcs').text('0');
}
});
$('#grand-total').text('रू 0.00');
updateSerialNumbers();
}
// Event listeners for dynamic updates
$('#products').on('input', 'input', function() {
const row = $(this).closest('.product-row');
updateTotalDispatch(row);
updateSoldStock(row);
});< /code>
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap');
body {
font-family: 'Roboto', sans-serif;
margin: 20px;
font-size: 14px;
color: #333;
background-color: beige;
}
.header {
margin-bottom: 20px;
background-color: white;
padding: 15px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.center-title {
text-align: center;
margin: 5px 0;
}
h2 {
font-size: 18px;
font-weight: 500;
}
h3,
h4 {
font-size: 16px;
font-weight: 400;
}
table {
width: 70%;
border-collapse: collapse;
margin-top: 10px;
background-color: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
th,
td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
font-size: 13px;
}
th {
background-color: #f2f2f2;
font-weight: 500;
}
input[type="text"],
input[type="number"],
input[type="date"] {
border: 1px solid #ccc;
padding: 5px;
border-radius: 4px;
font-size: 13px;
background-color: #f9f9f9;
}
input:focus {
border-color: #007BFF;
outline: none;
background-color: white;
}
.total-row {
font-weight: bold;
text-align: right;
}
.buttons {
margin-top: 20px;
display: flex;
gap: 10px;
}
button {
padding: 4px 8px;
font-size: 13px;
border: none;
border-radius: 4px;
background-color: #007BFF;
color: white;
cursor: pointer;
transition: background-color 0.3s ease;
}
button:hover {
background-color: #0056b3;
}
.total-amount {
font-weight: bold;
color: #007BFF;
}
.stock-inputs,
.added-inputs,
.return-inputs,
.rate-qty-inputs {
display: flex;
gap: 5px;
align-items: center;
}
.rate-table {
width: 100%;
margin-top: 10px;
border-collapse: collapse;
}
.rate-table th,
.rate-table td {
border: 1px solid #ddd;
padding: 5px;
font-size: 12px;
}
.rate-table th {
background-color: #f2f2f2;
}
.stacked {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
}
.stacked span {
display: block;
text-align: center;
}
.select2-container {
width: 200px !important;
margin-bottom: 5px;
}
.select2-product {
width: 180px !important;
}
/* Fix for button spacing */
.product-row td:last-child {
display: flex;
gap: 5px;
/* Adds spacing between buttons */
}
/* Style for Add Rate and Remove Rate buttons */
.add-rate,
.remove-rate {
margin-right: 5px;
/* Adds spacing between buttons */
}< /code>
Store
Bau
[h4]Daily Challan[/h4]
Salesman Name:
Challan Date:
Challan Miti:
Challan No:
S.No.
Product Name
Stock in Van (Unit/Pcs)
Stock Added (Unit/Pcs)
Stock Return (Unit/Pcs)
Total Dispatch
Stock Sold (Unit/Pcs)
Rate per Unit (रू)
Total Amount (रू)
Actions
1
-
-
-
0 Unit
0 Pcs
0 Unit
0 Pcs
Quantity (Unit/Pcs)
Rate (रू)
All
Add Rate
Remove Rate
रू 0.00
Add Product
Remove Product
Back to Challan CRUD
Calculate Total
Save Challan
Cancel
Grand Total:
रू 0.00
< /code>
< /div>
< /div>
< /p>
Mein Backend: < /p>
from flask import Flask, request, jsonify, render_template
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://postgres:Twlight20@localhost/inventory_db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
# Models
class Product(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), unique=True, nullable=False)
primary_unit = db.Column(db.String(20), nullable=False) # ctn, sck, etc.
conversion_rate = db.Column(db.Integer, nullable=False) # pieces per unit
class Salesman(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), unique=True, nullable=False)
class Challan(db.Model):
id = db.Column(db.Integer, primary_key=True)
date = db.Column(db.Date, nullable=False)
miti = db.Column(db.String(10), nullable=False)
challan_no = db.Column(db.String(50), unique=True, nullable=False)
salesman_id = db.Column(db.Integer, db.ForeignKey('salesman.id'), nullable=False)
total_amount = db.Column(db.Float, nullable=False)
items = db.relationship('ChallanItem', backref='challan', cascade="all, delete-orphan")
class ChallanItem(db.Model):
id = db.Column(db.Integer, primary_key=True)
challan_id = db.Column(db.Integer, db.ForeignKey('challan.id'), nullable=False)
product_id = db.Column(db.Integer, db.ForeignKey('product.id'), nullable=False)
stock_van = db.Column(db.JSON, nullable=False)
stock_added = db.Column(db.JSON, nullable=False)
stock_return = db.Column(db.JSON, nullable=False)
rates = db.Column(db.JSON, nullable=False)
# Routes
@app.route('/')
def index():
return render_template('challan.html')
# Get all or search products
@app.route('/api/products', methods=['GET'])
def get_products():
search = request.args.get('q', '').strip()
if search:
products = Product.query.filter(Product.name.ilike(f'%{search}%')).all()
else:
products = Product.query.all()
if not products:
return jsonify({'message': 'No products found'}), 404
return jsonify([{'id': p.id, 'name': p.name, 'primary_unit': p.primary_unit, 'conversion_rate': p.conversion_rate} for p in products])
# Get a single product by ID
@app.route('/api/product/', methods=['GET'])
def get_product(product_id):
product = Product.query.get(product_id)
if not product:
return jsonify({'error': 'Product not found'}), 404
return jsonify({
'id': product.id,
'name': product.name,
'primary_unit': product.primary_unit,
'conversion_rate': product.conversion_rate
})
# Get all or search salesmen
@app.route('/api/salesmen', methods=['GET'])
def get_salesmen():
search = request.args.get('q', '').strip()
if search:
salesmen = Salesman.query.filter(Salesman.name.ilike(f'%{search}%')).all()
else:
salesmen = Salesman.query.all()
if not salesmen:
return jsonify({'message': 'No salesmen found'}), 404
return jsonify([{'id': s.id, 'name': s.name} for s in salesmen])
# Get a single salesman by ID
@app.route('/api/salesman/', methods=['GET'])
def get_salesman(salesman_id):
salesman = Salesman.query.get(salesman_id)
if not salesman:
return jsonify({'error': 'Salesman not found'}), 404
return jsonify({'id': salesman.id, 'name': salesman.name})
# Save a new Challan
@app.route('/api/challan', methods=['POST'])
def save_challan():
data = request.json
# Validate required fields
required_fields = ['date', 'miti', 'challan_no', 'salesman_id', 'total_amount', 'items']
for field in required_fields:
if field not in data:
return jsonify({'error': f'Missing field: {field}'}), 400
if not isinstance(data['items'], list) or len(data['items']) == 0:
return jsonify({'error': 'Items must be a non-empty list'}), 400
# Ensure the salesman exists
salesman = Salesman.query.get(data['salesman_id'])
if not salesman:
return jsonify({'error': 'Invalid salesman ID'}), 400
# Ensure the challan number is unique
existing_challan = Challan.query.filter_by(challan_no=data['challan_no']).first()
if existing_challan:
return jsonify({'error': 'Challan number already exists'}), 400
challan = Challan(
date=data['date'],
miti=data['miti'],
challan_no=data['challan_no'],
salesman_id=data['salesman_id'],
total_amount=data['total_amount']
)
for item in data['items']:
# Validate each item
required_item_fields = ['product_id', 'stock_van', 'stock_added', 'stock_return', 'rates']
for field in required_item_fields:
if field not in item:
return jsonify({'error': f'Missing item field: {field}'}), 400
# Ensure the product exists
product = Product.query.get(item['product_id'])
if not product:
return jsonify({'error': f'Invalid product ID: {item["product_id"]}'}), 400
challan_item = ChallanItem(
product_id=item['product_id'],
stock_van=item['stock_van'],
stock_added=item['stock_added'],
stock_return=item['stock_return'],
rates=item['rates']
)
challan.items.append(challan_item)
db.session.add(challan)
db.session.commit()
return jsonify({'message': 'Challan saved successfully', 'challan_id': challan.id})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
1739029012
Guest
Ich habe eine Rechnung, um das Datum für die PostgreSQL -Datenbank zu sammeln, und ich habe Probleme, mit Endpunkten ordnungsgemäß zu arbeiten und die Dropdown -Liste für das Feld des Verkaufsnamens und des Produktnamens zu bevölkern. JSON -Antwort und es liefert das Frontend mit Sätzen, während mit meinem anderen Backend -Code die JSON -Antworten gültig waren, die Select2 konnte nicht ausgefüllt werden und dann den geteilten Namen im Feld beibehalten. < /p> Frontend < /p> [code]// Initialize Select2 components $(document).ready(function() { $('#salesman').select2({ placeholder: 'Select Salesman', ajax: { url: '/api/salesmen', dataType: 'json', delay: 250, processResults: data => ({ results: data }) } }); initProductSelect($('.product-row').first().find('.product-name')); }); function initProductSelect(element) { element.select2({ placeholder: 'Select Product', ajax: { url: '/api/products', dataType: 'json', delay: 250, processResults: data => ({ results: data }) } }).on('select2:select', async function(e) { const row = $(this).closest('.product-row'); try { const response = await fetch(`/api/product/${e.params.data.id}`); const product = await response.json(); row.data('conversion', product.conversion_rate) .find('.stock-ctn, .added-ctn, .return-ctn').attr('placeholder', product.primary_unit) .closest('tr').find('.total-dispatch-ctn, .sold-ctn').text(`0 ${product.primary_unit}`); } catch (error) { console.error('Error loading product:', error); } }); } function convertToPieces(ctn, pcs, row) { return (ctn * row.data('conversion')) + (parseInt(pcs) || 0); } function updateTotalDispatch(row) { const $row = $(row); const conversion = $row.data('conversion') || 1; const stockCtn = parseInt($row.find('.stock-ctn').val()) || 0; const stockPcs = parseInt($row.find('.stock-pcs').val()) || 0; const addedCtn = parseInt($row.find('.added-ctn').val()) || 0; const addedPcs = parseInt($row.find('.added-pcs').val()) || 0; const totalPieces = (stockCtn + addedCtn) * conversion + stockPcs + addedPcs; $row.find('.total-dispatch-ctn').text(`${Math.floor(totalPieces/conversion)} ${$row.find('.stock-ctn').attr('placeholder')}`); $row.find('.total-dispatch-pcs').text(`${totalPieces % conversion} Pcs`); } function updateSoldStock(row) { const $row = $(row); const conversion = $row.data('conversion') || 1; // Parse inputs with fallback to 0 if empty or invalid const stockCtn = parseInt($row.find('.stock-ctn').val()) || 0; const stockPcs = parseInt($row.find('.stock-pcs').val()) || 0; const addedCtn = parseInt($row.find('.added-ctn').val()) || 0; const addedPcs = parseInt($row.find('.added-pcs').val()) || 0; const returnCtn = parseInt($row.find('.return-ctn').val()) || 0; const returnPcs = parseInt($row.find('.return-pcs').val()) || 0; // Calculate total stock and return const totalStock = (stockCtn + addedCtn) * conversion + (stockPcs + addedPcs); const totalReturn = returnCtn * conversion + returnPcs; // Validate return doesn't exceed stock if (totalReturn > totalStock) { const maxReturn = totalStock; $row.find('.return-ctn').val(Math.floor(maxReturn / conversion)); $row.find('.return-pcs').val(maxReturn % conversion); alert("Return exceeds available stock!"); return; } // Calculate sold stock const soldPcs = totalStock - totalReturn; const soldCtn = Math.floor(soldPcs / conversion); const soldRemainingPcs = soldPcs % conversion; // Update display $row.find('.sold-ctn').text(`${soldCtn} ${$row.find('.stock-ctn').attr('placeholder')}`); $row.find('.sold-pcs').text(`${soldRemainingPcs} Pcs`); // Disable "Add Rate" button if all stock is allocated let totalAllocated = 0; $row.find('.rate-rows tr').each(function() { const rateCtn = parseInt($(this).find('.rate-ctn').val()) || 0; const ratePcs = parseInt($(this).find('.rate-pcs').val()) || 0; totalAllocated += rateCtn * conversion + ratePcs; }); $row.find('.add-rate').prop('disabled', totalAllocated >= soldPcs); } function addRate(button) { const row = $(button).closest('.product-row'); const rateRows = row.find('.rate-rows'); const soldPcs = convertToPieces( parseInt(row.find('.sold-ctn').text()), parseInt(row.find('.sold-pcs').text()), row ); let totalAllocated = 0; rateRows.find('tr').each(function() { totalAllocated += convertToPieces( parseInt($(this).find('.rate-ctn').val()), parseInt($(this).find('.rate-pcs').val()), row ); }); if (totalAllocated >= soldPcs) { alert("Allocated quantity exceeds sold stock!"); return; } const newRow = $('').html(` - ${rateRows.children().length === 0 ? '' : ''} `); rateRows.append(newRow); updateSoldStock(row); } function removeRate(button) { const row = $(button).closest('.product-row'); const rateRows = row.find('.rate-rows'); const lastRateRow = rateRows.find('tr').last(); if (lastRateRow.length > 0) { lastRateRow.remove(); // Remove the last rate row updateSoldStock(row); // Update sold stock after removal } else { alert("No rate rows to remove!"); } } function selectAllQuantity(checkbox) { const row = $(checkbox).closest('.product-row'); const rateRow = $(checkbox).closest('tr'); const conversion = row.data('conversion'); const soldCtn = parseInt(row.find('.sold-ctn').text()); const soldPcs = parseInt(row.find('.sold-pcs').text()); if (checkbox.checked) { rateRow.find('.rate-ctn').val(soldCtn).prop('disabled', true); rateRow.find('.rate-pcs').val(soldPcs).prop('disabled', true); } else { rateRow.find('.rate-ctn, .rate-pcs').val('').prop('disabled', false); } } function addProductRow() { const newRow = $('.product-row').first().clone(); newRow.find('input').val(''); newRow.find('.select2-product').remove(); newRow.find('td:first').html(''); newRow.find('.serial-number').text($('.product-row').length + 1); newRow.find('.rate-rows').empty(); newRow.find('.total-amount, .sold-ctn, .sold-pcs, .total-dispatch-ctn, .total-dispatch-pcs').text('0'); $('#products > tbody').append(newRow); initProductSelect(newRow.find('.product-name')); updateSerialNumbers(); } function removeProductRow(button) { const rows = $('.product-row'); if (rows.length === 1) { rows.find('input').val(''); rows.find('.rate-rows').empty(); rows.find('.select2-product').val(null).trigger('change'); updateSoldStock(rows); updateTotalDispatch(rows); } else { $(button).closest('.product-row').remove(); } updateSerialNumbers(); } function updateSerialNumbers() { $('.product-row .serial-number').each((i, el) => $(el).text(i + 1)); } function calculateTotal() { let grandTotal = 0; $('.product-row').each(function() { let rowTotal = 0; $(this).find('.rate-rows tr').each(function() { const ctn = parseInt($(this).find('.rate-ctn').val()) || 0; const pcs = parseInt($(this).find('.rate-pcs').val()) || 0; const rate = parseFloat($(this).find('.rate-value').val()) || 0; const conversion = $(this).closest('.product-row').data('conversion') || 1; rowTotal += ((ctn * conversion) + pcs) * rate; }); $(this).find('.total-amount').text(`रू ${rowTotal.toFixed(2)}`); grandTotal += rowTotal; }); $('#grand-total').text(`रू ${grandTotal.toFixed(2)}`); } async function saveChallan() { const challanData = { date: $('#date').val(), miti: $('#challanMiti').val(), challan_no: $('#challanNo').val(), salesman_id: $('#salesman').val(), items: [], total_amount: parseFloat($('#grand-total').text().replace('रू ', '')) }; $('.product-row').each(function() { const $row = $(this); const item = { product_id: $row.find('.product-name').val(), stock_van: { ctn: parseInt($row.find('.stock-ctn').val()) || 0, pcs: parseInt($row.find('.stock-pcs').val()) || 0 }, stock_added: { ctn: parseInt($row.find('.added-ctn').val()) || 0, pcs: parseInt($row.find('.added-pcs').val()) || 0 }, stock_return: { ctn: parseInt($row.find('.return-ctn').val()) || 0, pcs: parseInt($row.find('.return-pcs').val()) || 0 }, rates: [] }; $row.find('.rate-rows tr').each(function() { item.rates.push({ ctn: parseInt($(this).find('.rate-ctn').val()) || 0, pcs: parseInt($(this).find('.rate-pcs').val()) || 0, rate: parseFloat($(this).find('.rate-value').val()) || 0 }); }); challanData.items.push(item); }); try { const response = await fetch('/api/challan', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(challanData) }); const result = await response.json(); alert(result.message); if (response.ok) cancelForm(); } catch (error) { console.error('Error:', error); alert('Error saving challan'); } } function cancelForm() { $('#salesman').val(null).trigger('change'); $('#date, #challanMiti, #challanNo').val(''); $('.product-row').each(function(index) { if (index > 0) $(this).remove(); else { $(this).find('input').val(''); $(this).find('.rate-rows').empty(); $(this).find('.select2-product').val(null).trigger('change'); $(this).find('.total-amount, .sold-ctn, .sold-pcs, .total-dispatch-ctn, .total-dispatch-pcs').text('0'); } }); $('#grand-total').text('रू 0.00'); updateSerialNumbers(); } // Event listeners for dynamic updates $('#products').on('input', 'input', function() { const row = $(this).closest('.product-row'); updateTotalDispatch(row); updateSoldStock(row); });< /code> @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap'); body { font-family: 'Roboto', sans-serif; margin: 20px; font-size: 14px; color: #333; background-color: beige; } .header { margin-bottom: 20px; background-color: white; padding: 15px; border-radius: 8px; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .center-title { text-align: center; margin: 5px 0; } h2 { font-size: 18px; font-weight: 500; } h3, h4 { font-size: 16px; font-weight: 400; } table { width: 70%; border-collapse: collapse; margin-top: 10px; background-color: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } th, td { border: 1px solid #ddd; padding: 8px; text-align: left; font-size: 13px; } th { background-color: #f2f2f2; font-weight: 500; } input[type="text"], input[type="number"], input[type="date"] { border: 1px solid #ccc; padding: 5px; border-radius: 4px; font-size: 13px; background-color: #f9f9f9; } input:focus { border-color: #007BFF; outline: none; background-color: white; } .total-row { font-weight: bold; text-align: right; } .buttons { margin-top: 20px; display: flex; gap: 10px; } button { padding: 4px 8px; font-size: 13px; border: none; border-radius: 4px; background-color: #007BFF; color: white; cursor: pointer; transition: background-color 0.3s ease; } button:hover { background-color: #0056b3; } .total-amount { font-weight: bold; color: #007BFF; } .stock-inputs, .added-inputs, .return-inputs, .rate-qty-inputs { display: flex; gap: 5px; align-items: center; } .rate-table { width: 100%; margin-top: 10px; border-collapse: collapse; } .rate-table th, .rate-table td { border: 1px solid #ddd; padding: 5px; font-size: 12px; } .rate-table th { background-color: #f2f2f2; } .stacked { display: flex; flex-direction: column; align-items: center; gap: 2px; } .stacked span { display: block; text-align: center; } .select2-container { width: 200px !important; margin-bottom: 5px; } .select2-product { width: 180px !important; } /* Fix for button spacing */ .product-row td:last-child { display: flex; gap: 5px; /* Adds spacing between buttons */ } /* Style for Add Rate and Remove Rate buttons */ .add-rate, .remove-rate { margin-right: 5px; /* Adds spacing between buttons */ }< /code> Store Bau [h4]Daily Challan[/h4] Salesman Name: Challan Date: Challan Miti: Challan No: S.No. Product Name Stock in Van (Unit/Pcs) Stock Added (Unit/Pcs) Stock Return (Unit/Pcs) Total Dispatch Stock Sold (Unit/Pcs) Rate per Unit (रू) Total Amount (रू) Actions 1 - - - 0 Unit 0 Pcs 0 Unit 0 Pcs Quantity (Unit/Pcs) Rate (रू) All Add Rate Remove Rate रू 0.00 Add Product Remove Product Back to Challan CRUD Calculate Total Save Challan Cancel Grand Total: रू 0.00 < /code> < /div> < /div> < /p> Mein Backend: < /p> from flask import Flask, request, jsonify, render_template from flask_sqlalchemy import SQLAlchemy from flask_cors import CORS app = Flask(__name__) CORS(app) app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://postgres:Twlight20@localhost/inventory_db' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False db = SQLAlchemy(app) # Models class Product(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), unique=True, nullable=False) primary_unit = db.Column(db.String(20), nullable=False) # ctn, sck, etc. conversion_rate = db.Column(db.Integer, nullable=False) # pieces per unit class Salesman(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), unique=True, nullable=False) class Challan(db.Model): id = db.Column(db.Integer, primary_key=True) date = db.Column(db.Date, nullable=False) miti = db.Column(db.String(10), nullable=False) challan_no = db.Column(db.String(50), unique=True, nullable=False) salesman_id = db.Column(db.Integer, db.ForeignKey('salesman.id'), nullable=False) total_amount = db.Column(db.Float, nullable=False) items = db.relationship('ChallanItem', backref='challan', cascade="all, delete-orphan") class ChallanItem(db.Model): id = db.Column(db.Integer, primary_key=True) challan_id = db.Column(db.Integer, db.ForeignKey('challan.id'), nullable=False) product_id = db.Column(db.Integer, db.ForeignKey('product.id'), nullable=False) stock_van = db.Column(db.JSON, nullable=False) stock_added = db.Column(db.JSON, nullable=False) stock_return = db.Column(db.JSON, nullable=False) rates = db.Column(db.JSON, nullable=False) # Routes @app.route('/') def index(): return render_template('challan.html') # Get all or search products @app.route('/api/products', methods=['GET']) def get_products(): search = request.args.get('q', '').strip() if search: products = Product.query.filter(Product.name.ilike(f'%{search}%')).all() else: products = Product.query.all() if not products: return jsonify({'message': 'No products found'}), 404 return jsonify([{'id': p.id, 'name': p.name, 'primary_unit': p.primary_unit, 'conversion_rate': p.conversion_rate} for p in products]) # Get a single product by ID @app.route('/api/product/', methods=['GET']) def get_product(product_id): product = Product.query.get(product_id) if not product: return jsonify({'error': 'Product not found'}), 404 return jsonify({ 'id': product.id, 'name': product.name, 'primary_unit': product.primary_unit, 'conversion_rate': product.conversion_rate }) # Get all or search salesmen @app.route('/api/salesmen', methods=['GET']) def get_salesmen(): search = request.args.get('q', '').strip() if search: salesmen = Salesman.query.filter(Salesman.name.ilike(f'%{search}%')).all() else: salesmen = Salesman.query.all() if not salesmen: return jsonify({'message': 'No salesmen found'}), 404 return jsonify([{'id': s.id, 'name': s.name} for s in salesmen]) # Get a single salesman by ID @app.route('/api/salesman/', methods=['GET']) def get_salesman(salesman_id): salesman = Salesman.query.get(salesman_id) if not salesman: return jsonify({'error': 'Salesman not found'}), 404 return jsonify({'id': salesman.id, 'name': salesman.name}) # Save a new Challan @app.route('/api/challan', methods=['POST']) def save_challan(): data = request.json # Validate required fields required_fields = ['date', 'miti', 'challan_no', 'salesman_id', 'total_amount', 'items'] for field in required_fields: if field not in data: return jsonify({'error': f'Missing field: {field}'}), 400 if not isinstance(data['items'], list) or len(data['items']) == 0: return jsonify({'error': 'Items must be a non-empty list'}), 400 # Ensure the salesman exists salesman = Salesman.query.get(data['salesman_id']) if not salesman: return jsonify({'error': 'Invalid salesman ID'}), 400 # Ensure the challan number is unique existing_challan = Challan.query.filter_by(challan_no=data['challan_no']).first() if existing_challan: return jsonify({'error': 'Challan number already exists'}), 400 challan = Challan( date=data['date'], miti=data['miti'], challan_no=data['challan_no'], salesman_id=data['salesman_id'], total_amount=data['total_amount'] ) for item in data['items']: # Validate each item required_item_fields = ['product_id', 'stock_van', 'stock_added', 'stock_return', 'rates'] for field in required_item_fields: if field not in item: return jsonify({'error': f'Missing item field: {field}'}), 400 # Ensure the product exists product = Product.query.get(item['product_id']) if not product: return jsonify({'error': f'Invalid product ID: {item["product_id"]}'}), 400 challan_item = ChallanItem( product_id=item['product_id'], stock_van=item['stock_van'], stock_added=item['stock_added'], stock_return=item['stock_return'], rates=item['rates'] ) challan.items.append(challan_item) db.session.add(challan) db.session.commit() return jsonify({'message': 'Challan saved successfully', 'challan_id': challan.id}) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=True) [/code]