diff --git a/app/controllers/documents_controller.php b/app/controllers/documents_controller.php index a7f9fb44..b9c58126 100755 --- a/app/controllers/documents_controller.php +++ b/app/controllers/documents_controller.php @@ -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 } @@ -518,13 +616,45 @@ ENDINSTRUCTIONS; $this->set('newDoc', $newDoc); if ($this->Document->saveAll($newDoc)) { - $newid = $this->Document->id; - $this->Session->setFlash(__("Revision {$number_of_revisions} created", true)); - $this->redirect(array('action'=>'view',$newid)); - } else { - $this->Session->setFlash(__('The Document could not be saved. Please, try again.', true)); + $newid = $this->Document->id; + + // 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)); + } + } diff --git a/app/views/purchase_orders/index.ctp b/app/views/purchase_orders/index.ctp index 0d8221e3..21121c92 100755 --- a/app/views/purchase_orders/index.ctp +++ b/app/views/purchase_orders/index.ctp @@ -65,7 +65,8 @@ foreach ($purchaseOrders as $purchaseOrder): 0) { ?> View - + Revise + diff --git a/app/webroot/js/document_add_edit_20240421.js b/app/webroot/js/document_add_edit_20240421.js new file mode 100644 index 00000000..d6403479 --- /dev/null +++ b/app/webroot/js/document_add_edit_20240421.js @@ -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 + * Copypasta'd from add_edit_shipment.js. + */ + function addToList(modelName, id, value, ULelement) { + var thisLI = $('
  • '); + var thisButton = $(''); + thisButton.addClass('removeFromList'); + thisButton.button(); + + var thisHiddenInput = $(''); + + 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 = "
  • "+data[id]+"
  • "; + $("#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 + '
    Item Code: ' + data.item_code; + } + + if(data.item_description) { + descText = descText + '
    Item Description: ' + data.item_description + '
    '; + + } + + 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('
  • DELIVERY IS ESTIMATED AT '+deliveryTime+ ' '+deliveryTF+ ' FROM RECEIPT OF YOUR TECHNICALLY AND COMMERCIALLY CLEAR ORDER'); + commList.append('
  • PAYMENT TERMS: '+paymentTerms+'
  • '); + commList.append('
  • QUOTATION IS VALID FOR '+daysValid+' DAYS
  • '); + commList.append('
  • ALL PRICES ARE '+deliveryPoint+'
  • '); + commList.append('
  • EXCHANGE RATE: '+exchangeRate+'
  • '); + commList.append('
  • CUSTOMS DUTY INCLUDED AT: '+customsDuty+'
  • '); + commList.append('
  • GST 10% EXTRA
  • '); + commList.append('
  • WHEN PAYMENTS ARE MADE INTO OUR BANK ACCOUNT, BANK CHARGES ARE YOUR RESPONSIBILITY
  • '); + $('#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(''); + $('.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; + } + + + + +} diff --git a/docker-compose.yml b/docker-compose.yml index a08b2d0e..45e6115c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 diff --git a/sql/004_add_revision_purchase_orders.sql b/sql/004_add_revision_purchase_orders.sql index f79c5112..20c9d330 100644 --- a/sql/004_add_revision_purchase_orders.sql +++ b/sql/004_add_revision_purchase_orders.sql @@ -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;