2
0
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:
Lavissa
2023-06-17 13:55:25 +02:00
committed by GitHub
parent 61d2f452b2
commit bf707766b6
28 changed files with 1185 additions and 28 deletions

View File

@ -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.

View File

@ -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) {

View File

@ -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={}) {

View File

@ -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',
},

View File

@ -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',
}

View File

@ -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',
}