Overview
Comment:Invoice module: ID and reference added to quotation's items
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | invoice_module
Files: files | file ages | folders
SHA3-256: d61c451ab95f41bb3685b3ae3d5dbd5d850f0743eb3cb9e79d5e50245ece2172
User & Date: alinaar on 2023-02-23 18:45:31
Other Links: branch diff | manifest | tags
Context
2023-02-24
16:11
Invoice module: quotation creation/edition's strong validation implemented check-in: 8363c78855 user: alinaar tags: invoice_module
2023-02-23
18:45
Invoice module: ID and reference added to quotation's items check-in: d61c451ab9 user: alinaar tags: invoice_module
17:32
Invoice module: creation and edition forms merged + amounts display updated check-in: 5550c809b7 user: alinaar tags: invoice_module
Changes

Modified src/skel-dist/modules/invoice/details.html from [8b243d71f9] to [1e48361aa8].

93
94
95
96
97
98
99
100
101

102
103
104
105
106
107
108

109
110
111
112
113
114
115
				<li>Total : {{$total|floatval|money_currency:false}}</li>
			</ul>
		</fieldset>
		
		<fieldset>
			<legend><h2>Articles</h2></legend>
			{{if $items}}
				<table id="item_list">
					<tr>

						<th>Dénomination</th>
						<th>Description</th>
						<th>Prix unitaire</th>
						<th>Quantité</th>
					</tr>
					{{#foreach from=$items item='item'}}
						<tr>

							<td>{{$item.name}}</td>
							<td>{{$item.description}}</td>
							<td>{{$item.unit_price|intval|money:false}}</td>
							<td>{{$item.quantity}}</td>
						</tr>
					{{/foreach}}
				</table>







|

>







>







93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
				<li>Total : {{$total|floatval|money_currency:false}}</li>
			</ul>
		</fieldset>
		
		<fieldset>
			<legend><h2>Articles</h2></legend>
			{{if $items}}
				<table id="item_list" class="list">
					<tr>
						<th>Réf.</th>
						<th>Dénomination</th>
						<th>Description</th>
						<th>Prix unitaire</th>
						<th>Quantité</th>
					</tr>
					{{#foreach from=$items item='item'}}
						<tr>
							<td>{{$item.reference}}</td>
							<td>{{$item.name}}</td>
							<td>{{$item.description}}</td>
							<td>{{$item.unit_price|intval|money:false}}</td>
							<td>{{$item.quantity}}</td>
						</tr>
					{{/foreach}}
				</table>

Modified src/skel-dist/modules/invoice/edit.html from [7b985d001b] to [584e57d50d].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{{#restrict section="accounting" level="write"}}

{{* ==================== Header ==================== *}}

{{:admin_header title="Devis et factures" current="acc"}}
<nav class="tabs">
	<aside>
		{{:linkbutton href="edit.html" label="Nouveau devis" shape="plus"}}
	</aside>
	{{:include file='./include/main_nav_tabs.html'}}
</nav>
{{:include file='./include/constants.tpl' keep='type_labels,status_labels,status_options'}}
{{* {{:include file='./include/style.tpl'}} *}}

{{if $_GET.id}}
	{{#load id=$_GET.id}}
		{{:assign .="document"}}
	{{/load}}
	<h1>Édition du devis n°{{$document.key}} "{{$document.subject}}"</h1>
{{else}}












|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{{#restrict section="accounting" level="write"}}

{{* ==================== Header ==================== *}}

{{:admin_header title="Devis et factures" current="acc"}}
<nav class="tabs">
	<aside>
		{{:linkbutton href="edit.html" label="Nouveau devis" shape="plus"}}
	</aside>
	{{:include file='./include/main_nav_tabs.html'}}
</nav>
{{:include file='./include/constants.tpl' keep='type_labels,status_labels,status_options'}}
{{:include file='./include/style.tpl'}}

{{if $_GET.id}}
	{{#load id=$_GET.id}}
		{{:assign .="document"}}
	{{/load}}
	<h1>Édition du devis n°{{$document.key}} "{{$document.subject}}"</h1>
{{else}}
35
36
37
38
39
40
41

42
43
44
45
46
47
48
		{{:assign var='quantity' value=$item.quantity|replace:',':'.'}}
		{{if $unit_price < 0 || $unit_price != $unit_price|floatval|strtolower}} {{* Hack to check the data is a number *}}
			{{:assign var='errors.' value='Le prix saisi pour "%s" est invalide.'|args:$item.name}}
		{{/if}}
		{{if $quantity < 0 || $quantity != $quantity|floatval|strtolower}}{{:assign var='errors.' value='La quantité saisie pour "%s" est invalide.'|args:$item.name}}{{/if}}

		{{:assign var='computed_item' value=$item}}

		{{:assign var='computed_item.unit_price' value=$unit_price|money_int}}
		{{:assign var='computed_item.quantity' value=$quantity|floatval}}
		{{:assign var='items.' value=$computed_item}}
		{{:assign var='computed_total' value="%d + %d * %F"|args:$computed_total:$computed_item.unit_price:$computed_item.quantity|math}}

	{{/foreach}}








>







35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
		{{:assign var='quantity' value=$item.quantity|replace:',':'.'}}
		{{if $unit_price < 0 || $unit_price != $unit_price|floatval|strtolower}} {{* Hack to check the data is a number *}}
			{{:assign var='errors.' value='Le prix saisi pour "%s" est invalide.'|args:$item.name}}
		{{/if}}
		{{if $quantity < 0 || $quantity != $quantity|floatval|strtolower}}{{:assign var='errors.' value='La quantité saisie pour "%s" est invalide.'|args:$item.name}}{{/if}}

		{{:assign var='computed_item' value=$item}}
		{{:assign var='computed_item.id' value=$index|intval}}
		{{:assign var='computed_item.unit_price' value=$unit_price|money_int}}
		{{:assign var='computed_item.quantity' value=$quantity|floatval}}
		{{:assign var='items.' value=$computed_item}}
		{{:assign var='computed_total' value="%d + %d * %F"|args:$computed_total:$computed_item.unit_price:$computed_item.quantity|math}}

	{{/foreach}}

135
136
137
138
139
140
141

142



143
144
145
146
147
148
149
150
			{{/if}}
			</ul>
		{{if !$document}}<p>(La valeur pré-remplie de ces informations est modifiable dans la configuration du module.)</p>{{/if}}
		</fieldset>
	{{/if}}

	<p class="submit">

		{{:button type="submit" name="quotation_submit" label="Valider la création de devis" class="main"}}



	</p>
</form>

{{:admin_footer}}

{{else}}
	{{:error message="Seuls les membres avec accès en écriture à la comptabilité peuvent générer ce document."}}
{{/restrict}}







>
|
>
>
>








136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
			{{/if}}
			</ul>
		{{if !$document}}<p>(La valeur pré-remplie de ces informations est modifiable dans la configuration du module.)</p>{{/if}}
		</fieldset>
	{{/if}}

	<p class="submit">
		{{if !$document}}
			{{:button type="submit" name="quotation_submit" label="Valider la création de devis" class="main"}}
		{{else}}
			{{:button type="submit" name="quotation_submit" label="Modifier le devis" class="main"}}
		{{/if}}
	</p>
</form>

{{:admin_footer}}

{{else}}
	{{:error message="Seuls les membres avec accès en écriture à la comptabilité peuvent générer ce document."}}
{{/restrict}}

Modified src/skel-dist/modules/invoice/include/item_list.tpl from [01c4314796] to [d16e7ab4b7].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script type="text/javascript">
function compute_total() {
	let items = document.getElementById('item_list').getElementsByClassName('item');
	let total = 0.0;
	let unit_price;
	let quantity;

	for (let i = 0; i < items.length; ++i) {
		unit_price = items[i].children[2].firstChild.firstChild.value.replace(',', '.');
		quantity = items[i].children[3].firstChild.value.replace(',', '.');
		if (!isNaN(unit_price) && !isNaN(quantity) && unit_price > 0)
			total += parseFloat(unit_price) * parseFloat(quantity);
	}
	return total;
}

function refresh_total() {








|
|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<script type="text/javascript">
function compute_total() {
	let items = document.getElementById('item_list').getElementsByClassName('item');
	let total = 0.0;
	let unit_price;
	let quantity;

	for (let i = 0; i < items.length; ++i) {
		unit_price = items[i].children[3].firstChild.firstChild.value.replace(',', '.');
		quantity = items[i].children[4].firstChild.value.replace(',', '.');
		if (!isNaN(unit_price) && !isNaN(quantity) && unit_price > 0)
			total += parseFloat(unit_price) * parseFloat(quantity);
	}
	return total;
}

function refresh_total() {
67
68
69
70
71
72
73

74
75
76
77
78
79
80
81
82
83
84

85
86
87
88
89
90
91

<fieldset>
	<legend><h2>Liste des articles</h2></legend>
	<p id="item_list_no_item_message" style="display: {{if !$items}}block{{else}}none{{/if}};">Aucun article pour le moment.</p>
	<table id="item_list" class="list" style="display: {{if $items}}block{{else}}none{{/if}};">
		<thead>
			<tr>

				<th>Dénomination</th>
				<th>Description</th>
				<th>Prix unitaire</th>
				<th>Quantité</th>
				<th></th>
			</tr>
		</thead>
		<tbody>

		{{#foreach from=$items key='key' item='item'}}
			<tr id={{"item_%d_row"|args:$key}} class="item" data-item-id="{{$key}}">

				<td>{{:input type="text" name="items[%d][name]"|args:$key default=$item.name}}</td>
				<td>{{:input type="textarea" cols="50" rows="2" name="items[%s][description]"|args:$key default=$item.description class="full-width"}}</td>
				<td>{{:input type="money" name="items[%d][unit_price]"|args:$key default=$item.unit_price class="impact_total"}}</td>
				<td>{{:input type="number" name="items[%d][quantity]"|args:$key default=$item.quantity class="impact_total"}}</td>
				<td>{{:button name="item_%d_delete_button" label="" shape="delete" onclick="remove_item(%d)"|args:$key}}</td>
			</tr>
		{{/foreach}}







>











>







67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

<fieldset>
	<legend><h2>Liste des articles</h2></legend>
	<p id="item_list_no_item_message" style="display: {{if !$items}}block{{else}}none{{/if}};">Aucun article pour le moment.</p>
	<table id="item_list" class="list" style="display: {{if $items}}block{{else}}none{{/if}};">
		<thead>
			<tr>
				<th>Réf.</th>
				<th>Dénomination</th>
				<th>Description</th>
				<th>Prix unitaire</th>
				<th>Quantité</th>
				<th></th>
			</tr>
		</thead>
		<tbody>

		{{#foreach from=$items key='key' item='item'}}
			<tr id={{"item_%d_row"|args:$key}} class="item" data-item-id="{{$key}}">
				<td>{{:input type="text" name="items[%d][reference]"|args:$key default=$item.reference class="reference"}}</td>
				<td>{{:input type="text" name="items[%d][name]"|args:$key default=$item.name}}</td>
				<td>{{:input type="textarea" cols="50" rows="2" name="items[%s][description]"|args:$key default=$item.description class="full-width"}}</td>
				<td>{{:input type="money" name="items[%d][unit_price]"|args:$key default=$item.unit_price class="impact_total"}}</td>
				<td>{{:input type="number" name="items[%d][quantity]"|args:$key default=$item.quantity class="impact_total"}}</td>
				<td>{{:button name="item_%d_delete_button" label="" shape="delete" onclick="remove_item(%d)"|args:$key}}</td>
			</tr>
		{{/foreach}}

Modified src/skel-dist/modules/invoice/include/style.tpl from [066e53cb5e] to [693e89cd54].

1
2
3
4
5
6
<style>
#item_list th, #item_list td {
	width: 25%;
	padding: 0em 2em 0em 0em;
}
</style>

|
|
|


1
2
3
4
5
6
<style>
#item_list .reference {
	min-width: 0;
	max-width: 7em;
}
</style>

Modified src/skel-dist/modules/invoice/item_form.html from [6acd31765f] to [4b3ccf311a].

20
21
22
23
24
25
26


27
28
29
30
31
32
33
			input.size = 8;
			input.class = 'money';
			input.autocomplete = 'off';
		}
	}
	if (name === 'unit_price' || name === 'quantity')
		input.classList.add('impact_total');


	input.name = 'items[' + index + '][' + name + ']';
	input.id = 'item_' + name + '_' + index;
	return (input);
}

function generate_item_empty_line() {
	let row_line;







>
>







20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
			input.size = 8;
			input.class = 'money';
			input.autocomplete = 'off';
		}
	}
	if (name === 'unit_price' || name === 'quantity')
		input.classList.add('impact_total');
	if (name === 'reference')
		input.classList.add('reference');
	input.name = 'items[' + index + '][' + name + ']';
	input.id = 'item_' + name + '_' + index;
	return (input);
}

function generate_item_empty_line() {
	let row_line;
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
	row_line.setAttribute('data-item-id', index);
	return row_line;
}

function add_item() {
	let row_line;
	let td;
	let item_properties = [['name', 'text'], ['description', 'textarea'], ['unit_price', 'money'], ['quantity', 'number']];
	let input;
	let nobr;

	row_line = generate_item_empty_line();
	for (let i = 0; i < 4; ++i) {
		td = document.createElement('td');
		td.classList.add('item_' + item_properties[i][0]);
		input = generate_item_input(item_properties[i][1], item_properties[i][0], row_line.id);
		input.value = document.getElementById('item_' + item_properties[i][0]).value;
		if (item_properties[i][1] === 'money') {
			nobr = document.createElement('nobr');
			nobr.appendChild(input);
			input = nobr;
		}
		td.appendChild(input);
		row_line.appendChild(td);







|




|



|







43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
	row_line.setAttribute('data-item-id', index);
	return row_line;
}

function add_item() {
	let row_line;
	let td;
	let item_properties = [['reference', 'text'], ['name', 'text'], ['description', 'textarea'], ['unit_price', 'money'], ['quantity', 'number']];
	let input;
	let nobr;

	row_line = generate_item_empty_line();
	for (let i = 0; i < item_properties.length; ++i) {
		td = document.createElement('td');
		td.classList.add('item_' + item_properties[i][0]);
		input = generate_item_input(item_properties[i][1], item_properties[i][0], row_line.id);
		input.value = document.getElementById('f_item_' + item_properties[i][0]).value;
		if (item_properties[i][1] === 'money') {
			nobr = document.createElement('nobr');
			nobr.appendChild(input);
			input = nobr;
		}
		td.appendChild(input);
		row_line.appendChild(td);
73
74
75
76
77
78
79

80
81

82


83
84
85
86
87
88
89
}
</script>

<form method="POST" action="" id="item_creation_form" onsubmit="add_item()">
	<fieldset>
		<legend>Ajouter un article</legend>
		<ul>

			<li><label for="item_name">Dénomination</label><input type="text" name="item_name" id="item_name" label="" placeholder="ex : barnum" value="" /></li>
			<li><label for="item_description">Description</label><textarea name="item_description" id="item_description" label="" placeholder=""></textarea></li>

			<li><label for="item_unit_price">Prix unitaire</label><input type="text" name="item_unit_price" id="item_unit_price" label="" placeholder="" value="0" /></li>


			<li><label for="item_quantity">Quantité</label><input type="text" name="item_quantity" id="item_quantity" label="" placeholder="" value="1" /></li>
		</ul>
		<input type="submit" name="" id="item_addition_button" value="Ajouter l'article au devis" />
	</fieldset>
</form>

{{:admin_footer}}







>
|
|
>
|
>
>
|






75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
}
</script>

<form method="POST" action="" id="item_creation_form" onsubmit="add_item()">
	<fieldset>
		<legend>Ajouter un article</legend>
		<ul>
			<li>{{:input type="text" name="item_reference" label="Référence" placeholder="ex : S-812"}}</li>
			<li>{{:input type="text" name="item_name" label="Dénomination" placeholder="ex : Location vélo - Forfait six jours"}}</li>
			<li>{{:input type="textarea" name="item_description" label="Description"}}</li>
			<li>
				<label for="item_unit_price">Prix unitaire</label><input type="text" name="item_unit_price" id="f_item_unit_price" label="" placeholder="" value="0" />
				{{* Awaiting #eeb7d3e36ef4e3d7f2e8bc49c18c3c6b672f2e18 resolution {{:input type="money" name="item_unit_price" id="item_unit_price" label="Prix unitaire" default="0"}} *}}
			</li>
			<li>{{:input type="number" name="item_quantity" label="Quantité" default="1"}}</li>
		</ul>
		<input type="submit" name="" id="item_addition_button" value="Ajouter l'article au devis" />
	</fieldset>
</form>

{{:admin_footer}}

Modified src/skel-dist/modules/invoice/quotation.schema.json from [de28802c8e] to [0c8633bf2e].

29
30
31
32
33
34
35


36
37
38
39
40
41
42
43
44
45
46
47
48
		},
		"items": {
			"description": "Articles",
			"type": ["array", "null"],
			"items": {
				"type": "object",
				"properties": {


					"name": { "type": "string", "minLength": 1, "maxLength": 128 },
					"description": { "type": "string", "maxLength": 512 },
					"unit_price": { "type": "number", "minimum": 0 },
					"quantity": { "type": "number", "minimum": 0 }
				},
				"required" : ["name"]
			}
		},
		"total": {
			"description": "Total",
			"type": ["number", "integer"],
			"minimum": 0
		},







>
>





|







29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
		},
		"items": {
			"description": "Articles",
			"type": ["array", "null"],
			"items": {
				"type": "object",
				"properties": {
					"id": { "type": "integer" },
					"reference": { "type": "string", "maxLength": 128 },
					"name": { "type": "string", "minLength": 1, "maxLength": 128 },
					"description": { "type": "string", "maxLength": 512 },
					"unit_price": { "type": "number", "minimum": 0 },
					"quantity": { "type": "number", "minimum": 0 }
				},
				"required" : ["id", "name"]
			}
		},
		"total": {
			"description": "Total",
			"type": ["number", "integer"],
			"minimum": 0
		},