mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-18 13:05:42 +00:00
[Feature] Company Addresses (#4732)
* Add initial model structure * Initial Address model defined * Add migration and unit tests * Initial migration for Address model generated * Unit tests for Address model added * Move address field to new model * Added migration to move address field to Address model * Implement address feature to backend * API endpoints for list and detail implemented * Serializer class for Address implemented * Final migration to delete old address field from company added * Tests for API and migrations added * Amend migration file names * Fix migration names in test * Add address property to company model * Iinital view and JS code * Fix indents * Fix different things * Pre-emptive change before merge * Post-merge fixes * dotdotdot... * ... * iDots * . * . * . * Add form functionality and model checks * Forms require a confirmation slider to be checked to submit if address is selected as primary * Backend resets primary address before saving if new address is designated as primary * Fix pre-save logic to enforce primary uniqueness * Fix typos * Sort out migrations * Forgot one * Add admin entry and small fixes * Fix migration file name and dependency * Update InvenTree/company/models.py Co-authored-by: Matthias Mair <code@mjmair.com> * Update InvenTree/company/models.py Co-authored-by: Matthias Mair <code@mjmair.com> * Correct final issues * . --------- Co-authored-by: Matthias Mair <code@mjmair.com>
This commit is contained in:
@ -1,15 +1,19 @@
|
||||
{% load i18n %}
|
||||
|
||||
/* globals
|
||||
clearFormErrors,
|
||||
constructLabel,
|
||||
constructForm,
|
||||
enableSubmitButton,
|
||||
formatCurrency,
|
||||
formatDecimal,
|
||||
formatDate,
|
||||
handleFormErrors,
|
||||
handleFormSuccess,
|
||||
imageHoverIcon,
|
||||
inventreeGet,
|
||||
inventreePut,
|
||||
hideFormInput,
|
||||
loadTableFilters,
|
||||
makeDeleteButton,
|
||||
makeEditButton,
|
||||
@ -19,24 +23,29 @@
|
||||
renderLink,
|
||||
renderPart,
|
||||
setupFilterList,
|
||||
showFormInput,
|
||||
thumbnailImage,
|
||||
wrapButtons,
|
||||
*/
|
||||
|
||||
/* exported
|
||||
createAddress,
|
||||
createCompany,
|
||||
createContact,
|
||||
createManufacturerPart,
|
||||
createSupplierPart,
|
||||
createSupplierPartPriceBreak,
|
||||
deleteAddress,
|
||||
deleteContacts,
|
||||
deleteManufacturerParts,
|
||||
deleteManufacturerPartParameters,
|
||||
deleteSupplierParts,
|
||||
duplicateSupplierPart,
|
||||
editAddress,
|
||||
editCompany,
|
||||
editContact,
|
||||
editSupplierPartPriceBreak,
|
||||
loadAddressTable,
|
||||
loadCompanyTable,
|
||||
loadContactTable,
|
||||
loadManufacturerPartTable,
|
||||
@ -401,9 +410,6 @@ function companyFormFields() {
|
||||
website: {
|
||||
icon: 'fa-globe',
|
||||
},
|
||||
address: {
|
||||
icon: 'fa-envelope',
|
||||
},
|
||||
currency: {
|
||||
icon: 'fa-dollar-sign',
|
||||
},
|
||||
@ -782,6 +788,324 @@ function loadContactTable(table, options={}) {
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct a set of form fields for the Address model
|
||||
*/
|
||||
function addressFields(options={}) {
|
||||
|
||||
let fields = {
|
||||
company: {
|
||||
icon: 'fa-building',
|
||||
},
|
||||
primary: {
|
||||
onEdit: function(val, name, field, opts) {
|
||||
|
||||
if (val === false) {
|
||||
|
||||
hideFormInput("confirm_primary", opts);
|
||||
$('#id_confirm_primary').prop("checked", false);
|
||||
clearFormErrors(opts);
|
||||
enableSubmitButton(opts, true);
|
||||
|
||||
} else if (val === true) {
|
||||
|
||||
showFormInput("confirm_primary", opts);
|
||||
if($('#id_confirm_primary').prop("checked") === false) {
|
||||
handleFormErrors({'confirm_primary': 'WARNING: Setting this address as primary will remove primary flag from other addresses'}, field, {});
|
||||
enableSubmitButton(opts, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirm_primary: {
|
||||
help_text: "Confirm",
|
||||
onEdit: function(val, name, field, opts) {
|
||||
|
||||
if (val === true) {
|
||||
|
||||
clearFormErrors(opts);
|
||||
enableSubmitButton(opts, true);
|
||||
|
||||
} else if (val === false) {
|
||||
|
||||
handleFormErrors({'confirm_primary': 'WARNING: Setting this address as primary will remove primary flag from other addresses'}, field, {});
|
||||
enableSubmitButton(opts, false);
|
||||
}
|
||||
},
|
||||
css: {
|
||||
display: 'none'
|
||||
}
|
||||
},
|
||||
title: {},
|
||||
line1: {
|
||||
icon: 'fa-map'
|
||||
},
|
||||
line2: {
|
||||
icon: 'fa-map',
|
||||
},
|
||||
postal_code: {
|
||||
icon: 'fa-map-pin',
|
||||
},
|
||||
postal_city: {
|
||||
icon: 'fa-city'
|
||||
},
|
||||
province: {
|
||||
icon: 'fa-map'
|
||||
},
|
||||
country: {
|
||||
icon: 'fa-map'
|
||||
},
|
||||
shipping_notes: {
|
||||
icon: 'fa-shuttle-van'
|
||||
},
|
||||
internal_shipping_notes: {
|
||||
icon: 'fa-clipboard'
|
||||
},
|
||||
link: {
|
||||
icon: 'fa-link'
|
||||
}
|
||||
};
|
||||
|
||||
if (options.company) {
|
||||
fields.company.value = options.company;
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
/*
|
||||
* Launches a form to create a new Address
|
||||
*/
|
||||
function createAddress(options={}) {
|
||||
let fields = options.fields || addressFields(options);
|
||||
|
||||
constructForm('{% url "api-address-list" %}', {
|
||||
method: 'POST',
|
||||
fields: fields,
|
||||
title: '{% trans "Create New Address" %}',
|
||||
onSuccess: function(response) {
|
||||
handleFormSuccess(response, options);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Launches a form to edit an existing Address
|
||||
*/
|
||||
function editAddress(pk, options={}) {
|
||||
let fields = options.fields || addressFields(options);
|
||||
|
||||
constructForm(`{% url "api-address-list" %}${pk}/`, {
|
||||
fields: fields,
|
||||
title: '{% trans "Edit Address" %}',
|
||||
onSuccess: function(response) {
|
||||
handleFormSuccess(response, options);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Launches a form to delete one (or more) addresses
|
||||
*/
|
||||
function deleteAddress(addresses, options={}) {
|
||||
|
||||
if (addresses.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
function renderAddress(address) {
|
||||
return `
|
||||
<tr>
|
||||
<td>${address.title}</td>
|
||||
<td>${address.line1}</td>
|
||||
<td>${address.line2}</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
let rows = '';
|
||||
let ids = [];
|
||||
|
||||
addresses.forEach(function(address) {
|
||||
rows += renderAddress(address);
|
||||
ids.push(address.pk);
|
||||
});
|
||||
|
||||
let html = `
|
||||
<div class='alert alert-block alert-danger'>
|
||||
{% trans "All selected addresses will be deleted" %}
|
||||
</div>
|
||||
<table class='table table-striped table-condensed'>
|
||||
<tr>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Line 1" %}</th>
|
||||
<th>{% trans "Line 2" %}</th>
|
||||
</tr>
|
||||
${rows}
|
||||
</table>`;
|
||||
|
||||
constructForm('{% url "api-address-list" %}', {
|
||||
method: 'DELETE',
|
||||
multi_delete: true,
|
||||
title: '{% trans "Delete Addresses" %}',
|
||||
preFormContent: html,
|
||||
form_data: {
|
||||
items: ids,
|
||||
},
|
||||
onSuccess: function(response) {
|
||||
handleFormSuccess(response, options);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadAddressTable(table, options={}) {
|
||||
var params = options.params || {};
|
||||
|
||||
var filters = loadTableFilters('address', params);
|
||||
|
||||
setupFilterList('address', $(table), '#filter-list-addresses');
|
||||
|
||||
$(table).inventreeTable({
|
||||
url: '{% url "api-address-list" %}',
|
||||
queryParams: filters,
|
||||
original: params,
|
||||
idField: 'pk',
|
||||
uniqueId: 'pk',
|
||||
sidePagination: 'server',
|
||||
sortable: true,
|
||||
formatNoMatches: function() {
|
||||
return '{% trans "No addresses found" %}';
|
||||
},
|
||||
showColumns: true,
|
||||
name: 'addresses',
|
||||
columns: [
|
||||
{
|
||||
field: 'primary',
|
||||
title: '{% trans "Primary" %}',
|
||||
switchable: false,
|
||||
formatter: function(value) {
|
||||
let checked = '';
|
||||
if (value == true) {
|
||||
checked = 'checked="checked"';
|
||||
}
|
||||
return `<input type="checkbox" ${checked} disabled="disabled" value="${value? 1 : 0}">`;
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'title',
|
||||
title: '{% trans "Title" %}',
|
||||
sortable: true,
|
||||
switchable: false,
|
||||
},
|
||||
{
|
||||
field: 'line1',
|
||||
title: '{% trans "Line 1" %}',
|
||||
sortable: false,
|
||||
switchable: false,
|
||||
},
|
||||
{
|
||||
field: 'line2',
|
||||
title: '{% trans "Line 2" %}',
|
||||
sortable: false,
|
||||
switchable: false,
|
||||
},
|
||||
{
|
||||
field: 'postal_code',
|
||||
title: '{% trans "Postal code" %}',
|
||||
sortable: false,
|
||||
switchable: false,
|
||||
},
|
||||
{
|
||||
field: 'postal_city',
|
||||
title: '{% trans "Postal city" %}',
|
||||
sortable: false,
|
||||
switchable: false,
|
||||
},
|
||||
{
|
||||
field: 'province',
|
||||
title: '{% trans "State/province" %}',
|
||||
sortable: false,
|
||||
switchable: false,
|
||||
},
|
||||
{
|
||||
field: 'country',
|
||||
title: '{% trans "Country" %}',
|
||||
sortable: false,
|
||||
switchable: false,
|
||||
},
|
||||
{
|
||||
field: 'shipping_notes',
|
||||
title: '{% trans "Courier notes" %}',
|
||||
sortable: false,
|
||||
switchable: true,
|
||||
},
|
||||
{
|
||||
field: 'internal_shipping_notes',
|
||||
title: '{% trans "Internal notes" %}',
|
||||
sortable: false,
|
||||
switchable: true,
|
||||
},
|
||||
{
|
||||
field: 'link',
|
||||
title: '{% trans "External Link" %}',
|
||||
sortable: false,
|
||||
switchable: true,
|
||||
},
|
||||
{
|
||||
field: 'actions',
|
||||
title: '',
|
||||
sortable: false,
|
||||
switchable: false,
|
||||
visible: options.allow_edit || options.allow_delete,
|
||||
formatter: function(value, row) {
|
||||
var pk = row.pk;
|
||||
|
||||
let html = '';
|
||||
|
||||
if (options.allow_edit) {
|
||||
html += makeEditButton('btn-address-edit', pk, '{% trans "Edit Address" %}');
|
||||
}
|
||||
|
||||
if (options.allow_delete) {
|
||||
html += makeDeleteButton('btn-address-delete', pk, '{% trans "Delete Address" %}');
|
||||
}
|
||||
|
||||
return wrapButtons(html);
|
||||
}
|
||||
}
|
||||
],
|
||||
onPostBody: function() {
|
||||
// Edit button callback
|
||||
if (options.allow_edit) {
|
||||
$(table).find('.btn-address-edit').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
editAddress(pk, {
|
||||
onSuccess: function() {
|
||||
$(table).bootstrapTable('refresh');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Delete button callback
|
||||
if (options.allow_delete) {
|
||||
$(table).find('.btn-address-delete').click(function() {
|
||||
var pk = $(this).attr('pk');
|
||||
|
||||
var row = $(table).bootstrapTable('getRowByUniqueId', pk);
|
||||
|
||||
if (row && row.pk) {
|
||||
|
||||
deleteAddress([row], {
|
||||
onSuccess: function() {
|
||||
$(table).bootstrapTable('refresh');
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* Delete one or more ManufacturerPart objects from the database.
|
||||
* - User will be provided with a modal form, showing all the parts to be deleted.
|
||||
|
@ -2225,7 +2225,16 @@ function constructField(name, parameters, options={}) {
|
||||
hover_title = ` title='${parameters.help_text}'`;
|
||||
}
|
||||
|
||||
html += `<div id='div_id_${field_name}' class='${form_classes}' ${hover_title}>`;
|
||||
var css = '';
|
||||
|
||||
if (parameters.css) {
|
||||
let str = Object.keys(parameters.css).map(function(key) {
|
||||
return `${key}: ${parameters.css[key]};`;
|
||||
})
|
||||
css = ` style="${str}"`;
|
||||
}
|
||||
|
||||
html += `<div id='div_id_${field_name}' class='${form_classes}' ${hover_title} ${css}>`;
|
||||
|
||||
// Add a label
|
||||
if (!options.hideLabels) {
|
||||
|
@ -14,6 +14,7 @@
|
||||
renderBuild,
|
||||
renderCompany,
|
||||
renderContact,
|
||||
renderAddress,
|
||||
renderGroup,
|
||||
renderManufacturerPart,
|
||||
renderOwner,
|
||||
@ -52,6 +53,8 @@ function getModelRenderer(model) {
|
||||
return renderCompany;
|
||||
case 'contact':
|
||||
return renderContact;
|
||||
case 'address':
|
||||
return renderAddress;
|
||||
case 'stockitem':
|
||||
return renderStockItem;
|
||||
case 'stocklocation':
|
||||
@ -173,6 +176,17 @@ function renderContact(data, parameters={}) {
|
||||
}
|
||||
|
||||
|
||||
// Renderer for "Address" model
|
||||
function renderAddress(data, parameters={}) {
|
||||
return renderModel(
|
||||
{
|
||||
text: [data.title, data.country, data.postal_code, data.postal_city, data.province, data.line1, data.line2].filter(Boolean).join(', '),
|
||||
},
|
||||
parameters
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Renderer for "StockItem" model
|
||||
function renderStockItem(data, parameters={}) {
|
||||
|
||||
|
@ -126,6 +126,18 @@ function purchaseOrderFields(options={}) {
|
||||
return filters;
|
||||
}
|
||||
},
|
||||
address: {
|
||||
icon: 'fa-map',
|
||||
adjustFilters: function(filters) {
|
||||
let supplier = getFormFieldValue('supplier', {}, {modal: options.modal});
|
||||
|
||||
if (supplier) {
|
||||
filters.company = supplier;
|
||||
}
|
||||
|
||||
return filters;
|
||||
}
|
||||
},
|
||||
responsible: {
|
||||
icon: 'fa-user',
|
||||
},
|
||||
|
@ -90,6 +90,18 @@ function returnOrderFields(options={}) {
|
||||
return filters;
|
||||
}
|
||||
},
|
||||
address: {
|
||||
icon: 'fa-map',
|
||||
adjustFilters: function(filters) {
|
||||
let customer = getFormFieldValue('customer', {}, {modal: options.modal});
|
||||
|
||||
if (customer) {
|
||||
filters.company = customer;
|
||||
}
|
||||
|
||||
return filters;
|
||||
}
|
||||
},
|
||||
responsible: {
|
||||
icon: 'fa-user',
|
||||
}
|
||||
|
@ -116,6 +116,18 @@ function salesOrderFields(options={}) {
|
||||
return filters;
|
||||
}
|
||||
},
|
||||
address: {
|
||||
icon: 'fa-map',
|
||||
adjustFilters: function(filters) {
|
||||
let customer = getFormFieldValue('customer', {}, {modal: options.modal});
|
||||
|
||||
if (customer) {
|
||||
filters.company = customer;
|
||||
}
|
||||
|
||||
return filters;
|
||||
}
|
||||
},
|
||||
responsible: {
|
||||
icon: 'fa-user',
|
||||
}
|
||||
|
Reference in New Issue
Block a user