Implement PO revisions

This commit is contained in:
Karl Cordes 2025-06-23 08:19:11 +10:00
parent a45effbd2b
commit cd0e172984
5 changed files with 1035 additions and 12 deletions

View file

@ -445,6 +445,19 @@ ENDINSTRUCTIONS;
}
function getBaseTitle($titleWithRevision) {
// The pattern matches:
// - : the literal hyphen
// Rev : the literal string "Rev" (case-sensitive)
// \d+ : one or more digits (representing the revision number)
// $ : asserts that this pattern must be at the end of the string
//
// If "Rev" could be "rev", "REV", etc., you can make it case-insensitive
// by adding the 'i' flag: '/-Rev\d+$/i'
$baseTitle = preg_replace('/-Rev\d+$/', '', $titleWithRevision);
return $baseTitle;
}
/**
* Revise a Document.
@ -465,7 +478,48 @@ ENDINSTRUCTIONS;
$this->Document->create();
if(!empty($document['Invoice']['id'])) {
echo "WE HAVE AN INVOICE";
// Invoice revision
$newDoc = array();
// 1. Copy Document fields and update revision
$newDoc['Document'] = $this->unset_keys($document['Document'], array('id', 'created', 'modified'));
$newDoc['Document']['revision'] = $document['Document']['revision'] + 1;
// user_id for the new revision will be the current user
$currentUser = $this->GetCurrentUser(); // Assumes GetCurrentUser() is available
$newDoc['Document']['user_id'] = $currentUser['User']['id'];
// CakePHP will set created/modified timestamps automatically for the new record
// 2. Copy Invoice fields
$newDoc['Invoice'] = $this->unset_keys($document['Invoice'], array('id', 'document_id', 'created', 'modified'));
$newDoc['Invoice']['issue_date'] = date('Y-m-d');
$newDoc['Invoice']['due_date'] = date("Y-m-d", strtotime("+30 days"));
// Modify title for uniqueness, appending revision number
$orginalTitleParts = explode('-', $document['Invoice']['title']);
$newDoc['Invoice']['title'] = $orginalTitleParts[0] . '-REV' . $newDoc['Document']['revision'];
// 3. Copy DocPage records (if any)
if (!empty($document['DocPage'])) {
$newDoc['DocPage'] = $document['DocPage'];
foreach ($newDoc['DocPage'] as $index => $page) {
$newDoc['DocPage'][$index]['id'] = null; // New record
$newDoc['DocPage'][$index]['document_id'] = null; // Will be set by saveAll
}
}
// 4. Copy Job associations (if applicable - check your data model)
if (!empty($document['Job'])) {
$job_ids = array();
foreach ($document['Job'] as $job) {
$job_ids[] = $job['id'];
}
// This structure is typically used by saveAll for HABTM relationships
$newDoc['Job']['Job'] = $job_ids;
}
// Store info for flash message
// $this->set('revision_number_for_flash', $newDoc['Document']['revision']);
// $this->set('document_type_for_flash', 'Invoice');
}
else if (!empty($document['Quote']['id'])) {
@ -499,12 +553,56 @@ ENDINSTRUCTIONS;
}
else if (!empty($document['PurchaseOrder']['id'])) {
echo "WE ARE REVISING A PO";
// Purchase Order revision
$newDoc = array();
// 1. Copy Document fields and update revision
$newDoc['Document'] = $this->unset_keys($document['Document'], array('id', 'created', 'modified'));
$newDoc['Document']['revision'] = $document['Document']['revision'] + 1;
$newDoc['Document']['type'] = 'purchaseOrder'; // Ensure type is set
// user_id for the new revision will be the current user
$currentUser = $this->GetCurrentUser();
$newDoc['Document']['user_id'] = $currentUser['User']['id'];
// 2. Copy PurchaseOrder fields
$newDoc['PurchaseOrder'] = $this->unset_keys($document['PurchaseOrder'], array('id', 'document_id', 'created', 'modified'));
$newDoc['PurchaseOrder']['issue_date'] = date('Y-m-d');
// Modify title for uniqueness, appending revision number
$baseTitle = $this->getBaseTitle($document['PurchaseOrder']['title']);
$newDoc['PurchaseOrder']['title'] = $baseTitle . '-Rev' . $newDoc['Document']['revision'];
// 3. Copy DocPage records (if any)
if (!empty($document['DocPage'])) {
$newDoc['DocPage'] = array();
foreach ($document['DocPage'] as $page) {
$newPage = $this->unset_keys($page, array('id', 'document_id', 'created', 'modified'));
$newDoc['DocPage'][] = $newPage;
}
}
// 4. Handle Job associations (HABTM)
// First, we need to fetch the jobs associated with the original purchase order
$originalPO = $this->Document->PurchaseOrder->find('first', array(
'conditions' => array('PurchaseOrder.id' => $document['PurchaseOrder']['id']),
'contain' => array('Job')
));
if (!empty($originalPO['Job'])) {
$job_ids = array();
foreach ($originalPO['Job'] as $job) {
$job_ids[] = $job['id'];
}
// Store job IDs to be processed after the main save
$newDoc['_job_ids'] = $job_ids;
}
print_r($newDoc);
}
else if (!empty($document['OrderAcknowledgement']['id'])) {
echo "WE ARE REVISING An ORDER ACK";
//TODO Order Acknowledgement revision
}
@ -519,7 +617,39 @@ ENDINSTRUCTIONS;
if ($this->Document->saveAll($newDoc)) {
$newid = $this->Document->id;
$this->Session->setFlash(__("Revision {$number_of_revisions} created", true));
// Handle Purchase Order Job associations
if (!empty($document['PurchaseOrder']['id']) && !empty($newDoc['_job_ids'])) {
// Get the new PurchaseOrder ID
$newPurchaseOrder = $this->Document->PurchaseOrder->find('first', array(
'conditions' => array('PurchaseOrder.document_id' => $newid),
'fields' => array('PurchaseOrder.id')
));
if (!empty($newPurchaseOrder['PurchaseOrder']['id'])) {
$po_id = $newPurchaseOrder['PurchaseOrder']['id'];
// Method 1: Using CakePHP's HABTM save (recommended)
$this->Document->PurchaseOrder->id = $po_id;
// Method 2: If Method 1 doesn't work, use the SQL approach as a fallback
foreach($newDoc['_job_ids'] as $job_id) {
$query = "INSERT INTO `jobs_purchase_orders` (`job_id`, `purchase_order_id`) VALUES ('{$job_id}', '{$po_id}');";
$this->Document->query($query);
}
}
}
// Updated flash message logic
$revisionNumber = $newDoc['Document']['revision'];
$docTypeFullName = 'Unknown Document';
if (isset($newDoc['Document']['type'])) {
$docTypeFullName = $this->Document->getDocFullName($newDoc['Document']['type']);
} else if (isset($document['Document']['type'])) {
$docTypeFullName = $this->Document->getDocFullName($document['Document']['type']);
}
$this->Session->setFlash(__("Revision {$revisionNumber} of {$docTypeFullName} created", true));
$this->redirect(array('action'=>'view',$newid));
} else {
$this->Session->setFlash(__('The Document could not be saved. Please, try again.', true));

View file

@ -65,6 +65,7 @@ foreach ($purchaseOrders as $purchaseOrder):
<td>
<?php if($purchaseOrder['PurchaseOrder']['document_id'] > 0) { ?>
<a href="/documents/view/<?=$purchaseOrder['PurchaseOrder']['document_id']; ?>">View</a>
<a href="/documents/revise/<?=$purchaseOrder['PurchaseOrder']['document_id']; ?>">Revise</a>
<?php } ?>
</td>

View file

@ -0,0 +1,858 @@
/**
* Could (should) tidy this up to move the Quote/Invoice etc Specific
* functionality into seperate files.
*
*
* Karl - 20/5/2011
*/
$(function() {
/**
* A more generic way of handling the HABTM <ul> <li>[REMOVE BUTTON] NAME [HIDDEN INPUT]</li> </ul>
* Copypasta'd from add_edit_shipment.js.
*/
function addToList(modelName, id, value, ULelement) {
var thisLI = $('<li></li>');
var thisButton = $('<button>X</button>');
thisButton.addClass('removeFromList');
thisButton.button();
var thisHiddenInput = $('<input type="hidden">');
var modelString = '['+modelName+']';
thisHiddenInput.attr('name', 'data[PurchaseOrder]'+modelString+'[]');
thisHiddenInput.attr('value', id);
thisLI.attr('id', modelName+'ID_'+id);
thisLI.html(value);
thisLI.prepend(thisButton);
thisLI.append(thisHiddenInput);
ULelement.append(thisLI);
}
//Remove X button clicked.
$('.removeFromList').live('click', function() {
$(this).parent().remove();
});
// var config defined in global.js
loadLineItems();
var docID = $('#documentID').html();
$("#lineItemDetails").hide();
$( "#addLineItemModal" ).dialog({
autoOpen: false,
height: 900,
width: 600,
modal: true,
buttons: {
"Add Line Item": function() {
$('#LineItemDescription').ckeditor(function() {
this.updateElement();
});
var thisLineItemInputs = $('#LineItemAddForm').find('input,select,textarea');
$.post('/line_items/ajax_add', thisLineItemInputs, function(data) {
if(data == 'SUCCESS') {
loadLineItems();
$( "#addLineItemModal" ).dialog('close');
}
else {
alert("Line Item could not be saved")
$('#LineItemDescription').ckeditor(config);
}
});
},
Cancel: function() {
$( this ).dialog( "close" );
}
},
close: function() {
loadLineItems();
}
});
$( "#editLineItemModal" ).dialog({
autoOpen: false,
height: 900,
width: 600,
modal: true,
buttons: {
"Edit Line Item": function() {
$('#LineItemDescription').ckeditor(function() {
this.updateElement();
});
var thisLineItemInputs = $('#LineItemEditForm').find('input,select,textarea');
$.post('/line_items/ajax_edit', thisLineItemInputs, function(data) {
if(data == 'SUCCESS') {
$( "#editLineItemModal" ).dialog('close');
}
else {
alert("Line Item could not be saved")
$('#LineItemDescription').ckeditor(config);
}
});
},
Cancel: function() {
$( "#editLineItemModal" ).dialog('close');
}
},
close: function() {
loadLineItems();
}
});
$( "#QuoteDetails" ).dialog({
autoOpen: false,
height: 900,
width: 600,
modal: true,
buttons: {
"Edit Quote Details": function() {
$('#QuoteCommercialComments').ckeditor(function() {
this.updateElement();
this.destroy();
});
var quoteInputs = $('#QuoteEditForm').find('input,select,textarea');
$.post('/quotes/ajax_edit', quoteInputs, function(data) {
$( "#QuoteDetails" ).dialog('close');
});
},
Cancel: function() {
$( this ).dialog( "close" );
}
},
close: function() {
loadLineItems();
}
});
$( "#addJobConfirmation" ).dialog({
autoOpen: false,
height: 400,
width: 400,
modal: true,
buttons: {
"Create Order Acknowledgement": function() {
var documentID = $("#documentID").html();
//window.location.href = "/documents/convert_to_oa/"+documentID;
//var newOAform = $('#DocumentConvertToOaForm').find('input');
var newOAform = $('#DocumentConvertToOaForm');
newOAform.submit();
/*$.post('/documents/convert_to_oa', newOAform, function(data) {
if(data =='SUCCESS') {
$("#flashMessage").html("Invoice Saved Successfully");
}
else {
$("#flashMessage").html("Unable to Save Invoice");
}
$("#flashMessage").show();
loadLineItems();
});
*/
},
Cancel: function() {
$( this ).dialog( "close" );
}
},
close: function() {
}
});
$("#pageContentFactory").hide();
//Add a new Page Element.
$("#addPage").button().click(function(event) {
event.preventDefault();
newPage(false);
return false;
});
//Open the LineItem dialog
$(".addLineItem").button().click(function() {
$('#LineItemDescription').ckeditor(function() {
this.destroy();
});
$("#editLineItemModal").empty();
var nextItemNo = $(".lineItem").length;
nextItemNo++;
$.get('/line_items/add/'+docID, function(data) {
$("#addLineItemModal").html(data);
$("#LineItemItemNumber").val(nextItemNo); //Auto fill in the next Item No
$("#productDetails").hide();
$('#LineItemDescription').ckeditor(config);
showHideTextPrices();
$( "#addLineItemModal" ).dialog('open');
});
return false;
});
$(".editLineItem").live('click', function() {
$('#LineItemDescription').ckeditor(function() {
this.destroy();
});
$("#addLineItemModal").empty();
var thisLineItemID = $(this).parent('td').attr('id');
$.get('/line_items/edit/'+thisLineItemID, function(data) {
$("#editLineItemModal").html(data);
$("#productDetails").hide();
$('#LineItemDescription').ckeditor(config);
showHideTextPrices();
$( "#editLineItemModal" ).dialog('open');
});
});
var products = {};
$("#principleSelect").live('change',function() {
var principleID = getSelectedID('#principleSelect');
$("#productDetails").hide();
$.get('/documents/getProducts/'+principleID, function(data) {
$('#productsDiv').html(data);
var resp = $(data).filter(".products_json");
products = jQuery.parseJSON(resp.html());
});
});
//Search for a Product
$("#productSearch").live('change', function() {
productSearch();
});
$("#productSearchButton").live('click', function() {
productSearch();
});
function productSearch() {
var searchVal = $("#productSearch").val();
searchVal = searchVal.toLowerCase();
var param = "term="+searchVal;
$.getJSON("/products/autocomplete", param, function(data) {
$("#productList").empty();
for(var id in data) {
var link = "<li><a href=\"#\" class=\"search_product\" data-product-id=\""+id+"\">"+data[id]+"</a></li>";
$("#productList").append(link);
}
});
}
//Click on a search product name.
$(".search_product").live('click', function() {
var productID = $(this).data('product-id');
getProductDetails(productID);
return false;
});
$("#productSelect").live('change',function() {
var productID = getSelectedID('#productSelect');
getProductDetails(productID);
});
function getProductDetails(productID) {
$.get('/documents/getProductDetails/'+productID, function(data) {
$("#lineItemDetails").show();
$("#LineItemProductId").val(data.id);
$("#LineItemTitle").val(data.title);
var descText = '';
if(data.item_code) {
descText = descText + '<br><b>Item Code:</b> ' + data.item_code;
}
if(data.item_description) {
descText = descText + '<br><b>Item Description:</b> ' + data.item_description + '<br>';
}
descText = descText + data.description;
$("#LineItemDescription").val(descText);
}, "json");
}
//Autocomplete product title for adding lineItem
$( "#productAutocomplete" ).live('focus', function() {
$(this).autocomplete({
source: "/products/autocomplete",
minLength: 2,
select: function( event, ui ) {
console.log(ui);
},
appendTo: '#searchProducts'
});
});
$("#productAutocomplete").insertAfter();
// Initialize the editor.
// Callback function can be passed and executed after full instance creation.
$('.page').ckeditor(config);
$("#LineItemHasTextPrices").live('change', function() {
showHideTextPrices();
});
//Remove this Page
$(".removePage").live('click',function() {
$('.page').ckeditor(function() {
this.destroy();
});
$(this).parents(".docPage").remove();
$('.page').ckeditor(config);
});
$("#savePages").click(function() {
savePages();
});
var timeoutID = window.setTimeout(savePages, 30000);
$(".quickpricing").live('change', function() {
calculateQuickPrices();
});
$('.removeLineItem').live('click', function() {
var thisLineItemID = $(this).parent('td').attr('id');
$.post('/line_items/ajax_delete/'+thisLineItemID, function(data) {
loadLineItems();
});
});
$("#editQuoteDetails").click(function() {
var quoteID = $("#quoteID").html();
$('#QuoteCommercialComments').ckeditor(function() {
this.destroy();
});
$.get('/quotes/edit/'+quoteID, function(data) {
$("#QuoteDetails").html(data);
$('#QuoteCommercialComments').ckeditor(config);
$("#QuoteDetails").dialog('open');
});
});
// Fairly quick and easy way to make this select box editable. Good enough.
$("#QuotePaymentTermsOptions").live('change', function(data) {
selectedOption = $("#QuotePaymentTermsOptions").val();
$("#QuotePaymentTerms").val(selectedOption);
console.log(selectedOption);
});
$("#generateFirstPage").click(function() {
if($(".firstPage").length == 0) {
newPage(true);
}
else {
var confirmed = confirm("This will overwrite any changes you have made to the first page");
}
if(confirmed) {
$.get('/documents/generateFirstPage/'+docID, function(data) {
$(".firstPage").val(data);
savePages();
});
}
});
$("#pdfDocButton").click(function() {
window.location = $(this).data('url');
});
$("#emailDocButton").click(function() {
var confirmed = confirm("This will email this Document and all Attachments to the Contact. Are you sure you want to do this?");
if(confirmed) {
window.location = $(this).data('url');
}
});
//Invoice View
$('#shippingDetails').ckeditor(config);
$("#DocumentBillTo").ckeditor(config);
$("#DocumentShipTo").ckeditor(config);
$("#saveInvoiceButton").click(function() {
saveDocument('Invoice');
});
//OA View.
$("#saveOAButton").click(function() {
saveDocument('Order Acknowledgement');
//if the job has changed, the create invoice button wont work properly.
//window.location.reload(true);
$('.job-title').html($("#OrderAcknowledgementJobId :selected").text());
});
//This is fucked beyond all words.
$("#PurchaseOrderDeliverTo").ckeditor(config);
$("#PurchaseOrderOrderedFrom").ckeditor(config);
$("#PurchaseOrderShippingInstructions").ckeditor(config);
$("#PurchaseOrderDescription").ckeditor(config);
//PackingList View. Damn you past Karl.
$("#savePackingListButton").click(function() {
saveDocument('Packing List');
//if the job has changed, the create invoice button wont work properly.
//window.location.reload(true);
$('.job-title').html($("#PackingListJobId :selected").text());
});
//PurchaseOrder View. Damn you past Karl.
$("#savePurchaseOrderButton").click(function() {
saveDocument('Purchase Order');
});
// Changing PO principal, fill in the default fields.
$("#PurchaseOrderPrincipleId").live('change', function(d) {
var principleID = getSelectedID('#PurchaseOrderPrincipleId');
$.getJSON('/principles/defaults/'+principleID, function(data) {
console.log(data.Principle.po_ordered_from);
$('#PurchaseOrderOrderedFrom').val(data.Principle.po_ordered_from);
updateTextFields(); //Update the CKEditor instances.
});
});
// Changing Freight Forwarder text
$("#freightForwarderSelect").live('change', function(d) {
$("#PurchaseOrderFreightForwarderText").val(getSelectedText("#freightForwarderSelect"));
saveDocument('Purchase Order');
});
$("#createOA").click(function() {
$("#addJobConfirmation").dialog('open');
});
// Issue #56 - try to stop doubleclicks on button-links.
$(".button-link").one('click', function() {
location.href = $(this).data('href');
});
//Choosing an address
$(".billing_address").click(function() {
var address = $(this).next().html();
setAddress(address, '#DocumentBillTo', 'Bill To');
});
$(".shipping_address").click(function() {
var address = $(this).next().html();
setAddress(address, '#DocumentShipTo', 'Ship To');
});
//Fuck it. Copypaste. Autocompletion of jobs on PO document view
$( "#job_autocomplete" ).autocomplete({
source: "/jobs/autocomplete",
minLength: 2,
select: function( event, ui ) {
if($('#JobID_'+ui.item.id).length == 0) { //This Job is not already in the List.
addToList('Job', ui.item.id, ui.item.value, $('#jobsList'));
var jobs_val = $("#PurchaseOrderJobsText").val();
$("#PurchaseOrderJobsText").val(jobs_val +', '+ ui.item.value)
console.log(ui.item.id);
console.log(ui.item.value);
//POST the job ID to a method on the documents controller to add the LineItems
$.post('/documents/add_job_items_to_po/'+ui.item.id+'/'+$("#DocumentId").val(), function(data) {
loadLineItems();
saveDocument('Purchase Order');
});
}
}
});
});
$('#generateShippingInstructions').live('click', function(event) {
event.preventDefault();
saveDocument('Purchase Order');
$.post('/documents/generateShippingInstructions/'+$("#DocumentId").val(), function(data) {
$('#PurchaseOrderShippingInstructions').val(data);
updateTextFields(); //Update the CKEditor instances.
//$("#PurchaseOrderShippingInstructions").ckeditor(config);
});
});
$('.generateCommercialComments').live('click', function(event) {
event.preventDefault();
var deliveryTime = $("#QuoteDeliveryTime").val();
var deliveryTF = $("#QuoteDeliveryTimeFrame").val();
var paymentTerms = $("#QuotePaymentTerms").val();
var daysValid = $("#QuoteDaysValid").val();
var deliveryPoint = $("#QuoteDeliveryPoint").val();
var exchangeRate = $("#QuoteExchangeRate").val();
var customsDuty = $("#QuoteCustomsDuty").val();
deliveryTime = deliveryTime.toUpperCase();
paymentTerms = paymentTerms.toUpperCase();
deliveryPoint = deliveryPoint.toUpperCase();
var commComments = $("#commCommentsInitialString").clone();
var commList = commComments.find('ol');
commList.append('<li>DELIVERY IS ESTIMATED AT '+deliveryTime+ ' '+deliveryTF+ ' FROM RECEIPT OF YOUR TECHNICALLY AND COMMERCIALLY CLEAR ORDER');
commList.append('<li>PAYMENT TERMS: '+paymentTerms+'</li>');
commList.append('<li>QUOTATION IS VALID <u>FOR '+daysValid+' DAYS</li>');
commList.append('<li>ALL PRICES ARE <u>'+deliveryPoint+'</u></li>');
commList.append('<li>EXCHANGE RATE: '+exchangeRate+'</u></li>');
commList.append('<li>CUSTOMS DUTY INCLUDED AT: '+customsDuty+'</li>');
commList.append('<li>GST 10% EXTRA</li>');
commList.append('<li>WHEN PAYMENTS ARE MADE INTO OUR BANK ACCOUNT, BANK CHARGES ARE YOUR RESPONSIBILITY</li>');
$('#QuoteCommercialComments').val(commComments.html());
});
//Save the current document
//@param string documentName for message
function saveDocument(documentName) {
updateTextFields(); //Update the CKEditor instances.
var fields = $('#DocumentEditForm').find('input,select,textarea');
$.post('/documents/ajax_edit', fields, function(data) {
if(data =='SUCCESS') {
$("#flashMessage").html(documentName+" saved Successfully");
}
else {
$("#flashMessage").html("Unable to save "+documentName);
}
$("#flashMessage").show();
loadLineItems();
});
}
//Set the address given the addressFieldID and a label FieldName
function setAddress(address, addressFieldID, fieldName) {
console.log(address);
if($(addressFieldID).val() == '') {
$(addressFieldID).val(address);
}
else {
var c = confirm("Set " + fieldName+ " to this address?");
if(c) {
$(addressFieldID).val(address);
}
}
}
function showHideTextPrices() {
if( $('#LineItemHasTextPrices').val() == 1) {
$("#noCosting").hide();
$("#noCosting").find('input').val('');
$("#textPrices").show();
}
else {
$("#noCosting").show();
$("#textPrices").hide();
}
}
//I am a much better programmer now.
//FUCK YEAH!!
function updateTextFields() {
var fields = [
'#shippingDetails',
"#DocumentBillTo",
"#DocumentShipTo",
"#PurchaseOrderDeliverTo",
"#PurchaseOrderOrderedFrom",
"#PurchaseOrderShippingInstructions",
"#PurchaseOrderDescription"
];
for (i in fields) {
$(fields[i]).ckeditor(function() {
this.updateElement();
});
}
}
function newPage(firstPage) {
$('.page').ckeditor(function() {
this.destroy();
});
var newPage = $('#pageContentFactory').clone();
newPage.removeAttr('id');
newPage.show();
var pageCount = $('.docPage').length;
//alert(pageCount);
pageCount++;
var model = 'DocPage';
var field = 'content';
var ID = getCakeID(model,pageCount, field);
var name = getCakeName(model, pageCount, field);
newPage.find('label').attr('for', ID);
newPage.find('textarea').attr('id', ID).attr('name', name);
newPage.addClass('docPage');
if(firstPage == true) {
newPage.find('textarea').addClass('firstPage');
}
var hiddenName = getCakeName(model, pageCount, 'page_number');
newPage.append('<input type="hidden" name="'+hiddenName+'" value="'+pageCount+'">');
$('.pages').append(newPage);
$('.page').ckeditor(config);
}
function savePages() {
var docPages = $('#DocumentEditForm').find('input,select,textarea');
$('.page').ckeditor(function() {
this.updateElement();
});
$.post('/documents/ajax_edit', docPages, function(data) {
$("#flashMessage").html("Document saved");
$("#flashMessage").show();
loadLineItems();
});
}
function loadLineItems() {
var documentID = $("#documentID").html();
/*$.get('/line_items/getTable/'+documentID, function(data) {
$("#lineItems").html(data);
});*/
$.ajax({
url: '/line_items/getTable/'+documentID,
cache: false,
success: function(data) {
$("#lineItems").html(data);
}
});
}
function calculateQuickPrices() {
var quantity = $('#LineItemQuantity').val();
var gross_unit_price = $("#LineItemGrossUnitPrice").val();
var net_unit_price = $("#LineItemNetUnitPrice").val();
var discount_percent = $("#LineItemDiscountPercent").val();
var discount_amount_unit = $("#LineItemDiscountAmountUnit").val();
var discount_amount_total = $("#LineItemDiscountAmountTotal").val();
var gross_price = $("#LineItemGrossPrice").val();
var net_price = $("#LineItemNetPrice").val();
gross_price = quantity * gross_unit_price;
$("#LineItemGrossPrice").val(gross_price);
discount_amount_unit = (discount_percent/100) * gross_unit_price;
discount_amount_unit = discount_amount_unit.toFixed(2);
discount_amount_total = (discount_percent/100) * gross_price;
discount_amount_total = discount_amount_total.toFixed(2);
$("#LineItemDiscountAmountTotal").val(discount_amount_total);
net_price = gross_price - discount_amount_total;
$("#LineItemNetPrice").val(net_price);
$("#LineItemDiscountAmountUnit").val(discount_amount_unit);
net_unit_price = gross_unit_price - discount_amount_unit;
$("#LineItemNetUnitPrice").val(net_unit_price);
}
function calcNetPrice() {
var discountPercent = $("#discountPercent").val();
var unitPrice = $('#unitPrice').val();
var quantity = $('#LineItemQuantity').val();
var grossSellPrice = quantity * unitPrice;
//Calculate the Sale Discount amount.
var UnitDiscountAmount = (discountPercent/100) * unitPrice;
var TotalDiscountAmount = (discountPercent/100) * grossSellPrice;
UnitDiscountAmount = UnitDiscountAmount.toFixed(2);
TotalDiscountAmount = TotalDiscountAmount.toFixed(2);
$('#total_discountAmount').val(TotalDiscountAmount);
$('#discountAmountEach').val(UnitDiscountAmount);
$('#net_price_each').val(unitPrice - UnitDiscountAmount);
$('#grossPrice').val(grossSellPrice);
var netPrice = grossSellPrice - TotalDiscountAmount;
$('#netPrice').val(netPrice);
}
function checkNaN(value) {
if( isNaN(value) == true) {
return 0;
}
else {
return value;
}
}

View file

@ -8,13 +8,14 @@ services:
- ./conf/nginx-site.conf:/etc/nginx/conf.d/cmc.conf
# todo setup site config.
- ./userpasswd:/etc/nginx/userpasswd:ro
depends_on:
- cmc
restart: unless-stopped
networks:
- cmc-network
cmc:
image: cmc:latest
build:
context: .
dockerfile: Dockerfile
platform: linux/amd64
depends_on:
@ -24,6 +25,38 @@ services:
- ./app/webroot/attachments_files:/var/www/cmc-sales/app/webroot/attachments_files
networks:
- cmc-network
develop:
watch:
- action: rebuild
path: ./app
ignore:
- ./app/webroot
cmc-django: # Renamed service for clarity (optional, but good practice)
build: # Added build section
context: ./cmc-django # Path to the directory containing the Dockerfile
dockerfile: Dockerfile # Name of the Dockerfile
platform: linux/amd64 # Keep platform if needed for compatibility
container_name: cmc-django-web # Optional: Keep or change container name
command: uv run python cmcsales/manage.py runserver 0.0.0.0:8888 # Add command if needed
volumes:
- ./cmc-django:/app # Mount the Django project directory
# Keep other necessary volumes if they exist outside ./cmc-django
# Example: - ./app/webroot/pdf:/app/webroot/pdf # Adjust path if needed
# Example: - ./app/webroot/attachments_files:/app/webroot/attachments_files # Adjust path if needed
ports: # Add ports if the Django app needs to be accessed directly
- "8888:8888"
environment: # Add environment variables needed by Django
DJANGO_SETTINGS_MODULE: cmcsales.settings
DATABASE_HOST: db
DATABASE_PORT: 3306
DATABASE_NAME: cmc
DATABASE_USER: cmc
DATABASE_PASSWORD: xVRQI&cA?7AU=hqJ!%au
depends_on:
- db
networks:
- cmc-network
db:
image: mariadb:latest

View file

@ -1 +1,2 @@
ALTER TABLE purchase_orders ADD COLUMN parent_purchase_order_id int(11) NOT NULL default 0;
ALTER TABLE purchase_orders ADD COLUMN revised_purchse_order_id int(11) NOT NULL default 0;