feat(Customer): Ajoute la fonction de clonage d'entité Customer.
 feat(SignClient): Utilise Docuseal avec PDF pour la signature des devis.
 feat(AvatarController): Ajoute un endpoint pour le verrouillage de l'application.
 feat(IpWall): Ajoute un composant web pour bloquer l'accès basé sur l'IP.
🎨 style(admin.scss): Ajoute des styles pour le modal de paiement et les murs de sécurité.
 feat(RegisterPayment): Ajoute un composant pour enregistrer les paiements.
🐛 fix(DevisPdf): Corrige l'alignement des totaux dans le PDF du devis.
🔥 chore: Ajoute discord_bot/node_modules au .gitignore.
 feat(ExportComptable): Ajoute une commande pour exporter les données comptables.
 feat(LockdownWall): Ajoute un composant web pour le verrouillage de l'application.
🐛 fix(CustomerController): Corrige la copie des contacts lors du clonage du client.
 feat(SecurityWall): Ajoute un composant web pour activer/désactiver le filtre de confidentialité.
This commit is contained in:
Serreau Jovann
2025-09-27 12:03:00 +02:00
parent a5333199e9
commit 506302a91a
28 changed files with 821 additions and 54 deletions

2
.env
View File

@@ -59,7 +59,7 @@ OVH_SECRET=12239d273975b5ab53318907fb66d355
OVH_CUSTOMER=56c387eb9ca4b9a2de4d4d97fd3d7f22
DOCUSIGN_URL=https://signature.esy-web.dev/api
DOCUSIGN_KEY=52u82oCoiG79awGsuxLfJqhxYjg8mrJfAsJJHejRMFa
DOCUSIGN_KEY=pgAU116mCFmeF7WQSezHqxtZW8V1fgo31u5d2FXoaKe
STANCER_PRIVATE_KEY=stest_Rv4Hz8ae2wQdjnBVCays7wPo
STANCER_PUBLIC_KEY=ptest_raV5vZ51Lnp2DfBtu5TVs5o0

1
.gitignore vendored
View File

@@ -35,3 +35,4 @@ coverage/
script/demande/hosts.ini
backup/*.zip
backup/*.sql
discord_bot/node_modules

View File

@@ -6,6 +6,9 @@ import {AutoCustomer} from './class/AutoCustomer'
import {RepeatLine} from './class/RepeatLine'
import {RegisterPayment} from './class/RegisterPayment'
import {OrderCtrl} from './class/OrderCtrl'
import {LockdownWall} from './class/LockdownWall'
import {SecurityWall} from './class/SecurityWall'
import {IpWall} from './class/IpWall'
import preactCustomElement from './functions/preact'
@@ -14,8 +17,11 @@ function script() {
customElements.define('server-card',ServerCard,{extends:'div'})
customElements.define('auto-customer',AutoCustomer,{extends:'button'})
customElements.define('repeat-line',RepeatLine,{extends:'div'})
customElements.define('lockdown-wall',LockdownWall,{})
customElements.define('security-wall',SecurityWall,{})
customElements.define('ip-wall',IpWall,{})
customElements.define('order-ctrl',OrderCtrl,{extends:'div'})
customElements.define("register-payment",RegisterPayment,{extends:'a'})
customElements.define("register-payment",RegisterPayment,{extends:'button'})
}
function full() {

View File

@@ -310,3 +310,103 @@ input {
margin: 0.25rem;
height: 90%;
}
.modal-payment{
z-index: 9000;
position: fixed;
top: 0;
left: 0;
background: rgba(0,0,0,0.5);
backdrop-filter: blur(5px);
width: 100%;
height: 100%;
.modal-payment-content{
position: fixed;
top: 50%;
left: 50%;
background: #1a202c;
width: 50%;
transform: translate(-50%,-50%);
h2{
text-align: center;
font-size: 1.25rem;
padding: 0.5rem;
border-bottom: 1px solid #cdcdcd;
}
a {
width: fit-content !important;
position: absolute;
top: 5px;
right: 5px;
}
}
}
security-wall-item{
background: black;
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 5000;
.content{
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
span {
text-align: center;
display: block;
margin-top: 2rem;
}
h2 {
font-size: 2rem;
warn {
color: red;
font-weight: bolder;
}
}
}
}
lockdown-wall-item{
background: repeating-linear-gradient(
45deg,
black,
black 20px,
red 20px,
red 40px
);
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
z-index: 5000;
.content{
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.8); /* Fond noir semi-transparent */
color: white; /* Texte blanc */
padding: 2rem 3rem;
border-radius: 10px;
text-align: center;
font-family: Arial, sans-serif;
box-shadow: 0 0 15px rgba(255, 0, 0, 0.7); /* Ombre rouge pour souligner */
span {
text-align: center;
display: block;
margin-top: 2rem;
}
h2 {
font-size: 1.5rem;
warn {
color: red;
font-weight: bolder;
}
}
}
}

14
assets/class/IpWall.js Normal file
View File

@@ -0,0 +1,14 @@
export class IpWall extends HTMLElement {
connectedCallback() {
if (!document.querySelector('security-wall-item')) {
let wall = document.createElement('security-wall-item');
wall.innerHTML = `
<div class="content">
<h2><warn>/!\\ </warn> - Protection MAINFRAME - <warn>/!\\ </warn></h2>
<span>Votre IP n'est pas autorisée par le système !</span>
</div>
`;
document.body.appendChild(wall);
}
}
}

View File

@@ -0,0 +1,25 @@
export class LockdownWall extends HTMLElement {
connectedCallback() {
fetch("/artemis/lockdown")
.then((r) => r.json())
.then((result) => {
if(result.lockdown) {
if (!document.querySelector('lockdown-wall-item')) {
let sound = new Audio("/sound/alert.mp3")
let wall = document.createElement('lockdown-wall-item');
wall.innerHTML = `
<div class="content" style="padding: 20px; background: #ffcccc; color: #900; font-weight: bold; text-align: center; border: 2px solid #900; border-radius: 8px; max-width: 600px; margin: 30px auto; font-family: Arial, sans-serif;">
<h2><warn>/!\\ </warn> - Protection MAINFRAME - <warn>/!\\ </warn></h2>
<p>Verrouillage complet de l'application est en cours, aucune action n'est possible.</p>
</div>
`;
document.body.appendChild(wall);
setInterval(() => {
sound.play();
}, 30000)
}
}
});
}
}

View File

@@ -1,4 +1,4 @@
export class RegisterPayment extends HTMLAnchorElement {
export class RegisterPayment extends HTMLButtonElement {
connectedCallback() {
let element = this;
element.addEventListener('click', (event) => {
@@ -9,9 +9,49 @@ export class RegisterPayment extends HTMLAnchorElement {
modal.classList = "modal-payment"
modal.innerHTML =`
<div class="modal-payment-content">
<h2>Enregistér un paiement</h2>
<h2>Enregistrée un paiement</h2>
<a class="block dbclose bg-red-600 hover:bg-red-700 text-white px-3 py-1 rounded">Fermer</a>
<form method="post" class="content">
<div class="flex space-x-4" is="order-ctrl">
<fieldset class="form-group form-group--horizontal">
<div class="flex space-x-4">
<div class="flex-1">
<div class="form-field">
<div class="mb-1">
<label for="titre" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Type</label>
<select class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required>
<option value="CB">Carte Bancaire</option>
<option value="PREV">Prévélevement</option>
</select>
</div>
</div>
</div>
<div class="flex-1">
<div class="form-field">
<div class="mb-1">
<label for="titre" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Date</label>
<input type="date" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required />
</div>
</div>
</div>
<div class="flex-1">
<div class="form-field">
<div class="mb-1">
<label for="titre" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Montant</label>
<input type="number" step="0.01" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" required />
</div>
</div>
</div>
</div>
</fieldset>
</div>
</form>
</div>
`;
modal.querySelector('.dbclose').addEventListener('click',()=>{
modal.remove();
})
document.body.appendChild(modal);
})
}
}

View File

@@ -0,0 +1,40 @@
export class SecurityWall extends HTMLElement {
connectedCallback() {
this.render();
// Ecoute globale pour Alt+P
window.addEventListener('keydown', (event) => {
if (event.altKey && event.key.toLowerCase() === 'p') {
this.toggleWall();
}
});
}
render() {
const isEnabled = localStorage.getItem('mainframe-wallsecurity') === 'true';
if (!isEnabled) {
// Retirer si déjà présent
const existing = document.querySelector('security-wall-item');
if (existing) existing.remove();
return;
}
// Ajouter seulement si pas déjà présent
if (!document.querySelector('security-wall-item')) {
let wall = document.createElement('security-wall-item');
wall.innerHTML = `
<div class="content">
<h2><warn>/!\\ </warn> - Protection MAINFRAME - <warn>/!\\ </warn></h2>
<span>Filtre de confidentialité activée</span>
</div>
`;
document.body.appendChild(wall);
}
}
toggleWall() {
const current = localStorage.getItem('mainframe-wallsecurity') === 'true';
localStorage.setItem('mainframe-wallsecurity', current ? 'false' : 'true');
this.render();
}
}

34
discord_bot/.gitignore vendored Normal file
View File

@@ -0,0 +1,34 @@
# dependencies (bun install)
node_modules
# output
out
dist
*.tgz
# code coverage
coverage
*.lcov
# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# caches
.eslintcache
.cache
*.tsbuildinfo
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store

15
discord_bot/README.md Normal file
View File

@@ -0,0 +1,15 @@
# discord_bot
To install dependencies:
```bash
bun install
```
To run:
```bash
bun run index.ts
```
This project was created using `bun init` in bun v1.2.22. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.

139
discord_bot/bun.lock Normal file
View File

@@ -0,0 +1,139 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "discord_bot",
"dependencies": {
"discord.js": "^14.22.1",
"nodemon": "^3.1.10",
},
"devDependencies": {
"@types/bun": "latest",
},
"peerDependencies": {
"typescript": "^5",
},
},
},
"packages": {
"@discordjs/builders": ["@discordjs/builders@1.11.3", "", { "dependencies": { "@discordjs/formatters": "^0.6.1", "@discordjs/util": "^1.1.1", "@sapphire/shapeshift": "^4.0.0", "discord-api-types": "^0.38.16", "fast-deep-equal": "^3.1.3", "ts-mixer": "^6.0.4", "tslib": "^2.6.3" } }, "sha512-p3kf5eV49CJiRTfhtutUCeivSyQ/l2JlKodW1ZquRwwvlOWmG9+6jFShX6x8rUiYhnP6wKI96rgN/SXMy5e5aw=="],
"@discordjs/collection": ["@discordjs/collection@1.5.3", "", {}, "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ=="],
"@discordjs/formatters": ["@discordjs/formatters@0.6.1", "", { "dependencies": { "discord-api-types": "^0.38.1" } }, "sha512-5cnX+tASiPCqCWtFcFslxBVUaCetB0thvM/JyavhbXInP1HJIEU+Qv/zMrnuwSsX3yWH2lVXNJZeDK3EiP4HHg=="],
"@discordjs/rest": ["@discordjs/rest@2.6.0", "", { "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.1.1", "@sapphire/async-queue": "^1.5.3", "@sapphire/snowflake": "^3.5.3", "@vladfrangu/async_event_emitter": "^2.4.6", "discord-api-types": "^0.38.16", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w=="],
"@discordjs/util": ["@discordjs/util@1.1.1", "", {}, "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g=="],
"@discordjs/ws": ["@discordjs/ws@1.2.3", "", { "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.5.1", "@discordjs/util": "^1.1.0", "@sapphire/async-queue": "^1.5.2", "@types/ws": "^8.5.10", "@vladfrangu/async_event_emitter": "^2.2.4", "discord-api-types": "^0.38.1", "tslib": "^2.6.2", "ws": "^8.17.0" } }, "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw=="],
"@sapphire/async-queue": ["@sapphire/async-queue@1.5.5", "", {}, "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg=="],
"@sapphire/shapeshift": ["@sapphire/shapeshift@4.0.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21" } }, "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg=="],
"@sapphire/snowflake": ["@sapphire/snowflake@3.5.3", "", {}, "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ=="],
"@types/bun": ["@types/bun@1.2.22", "", { "dependencies": { "bun-types": "1.2.22" } }, "sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA=="],
"@types/node": ["@types/node@24.5.2", "", { "dependencies": { "undici-types": "~7.12.0" } }, "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ=="],
"@types/react": ["@types/react@19.1.13", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ=="],
"@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
"@vladfrangu/async_event_emitter": ["@vladfrangu/async_event_emitter@2.4.6", "", {}, "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA=="],
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
"bun-types": ["bun-types@1.2.22", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="],
"chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"discord-api-types": ["discord-api-types@0.38.26", "", {}, "sha512-xpmPviHjIJ6dFu1eNwNDIGQ3N6qmPUUYFVAx/YZ64h7ZgPkTcKjnciD8bZe8Vbeji7yS5uYljyciunpq0J5NSw=="],
"discord.js": ["discord.js@14.22.1", "", { "dependencies": { "@discordjs/builders": "^1.11.2", "@discordjs/collection": "1.5.3", "@discordjs/formatters": "^0.6.1", "@discordjs/rest": "^2.6.0", "@discordjs/util": "^1.1.1", "@discordjs/ws": "^1.2.3", "@sapphire/snowflake": "3.5.3", "discord-api-types": "^0.38.16", "fast-deep-equal": "3.1.3", "lodash.snakecase": "4.1.1", "magic-bytes.js": "^1.10.0", "tslib": "^2.6.3", "undici": "6.21.3" } }, "sha512-3k+Kisd/v570Jr68A1kNs7qVhNehDwDJAPe4DZ2Syt+/zobf9zEcuYFvsfIaAOgCa0BiHMfOOKQY4eYINl0z7w=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
"ignore-by-default": ["ignore-by-default@1.0.1", "", {}, "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA=="],
"is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
"lodash.snakecase": ["lodash.snakecase@4.1.1", "", {}, "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="],
"magic-bytes.js": ["magic-bytes.js@1.12.1", "", {}, "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA=="],
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"nodemon": ["nodemon@3.1.10", "", { "dependencies": { "chokidar": "^3.5.2", "debug": "^4", "ignore-by-default": "^1.0.1", "minimatch": "^3.1.2", "pstree.remy": "^1.1.8", "semver": "^7.5.3", "simple-update-notifier": "^2.0.0", "supports-color": "^5.5.0", "touch": "^3.1.0", "undefsafe": "^2.0.5" }, "bin": { "nodemon": "bin/nodemon.js" } }, "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw=="],
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"pstree.remy": ["pstree.remy@1.1.8", "", {}, "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w=="],
"readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"simple-update-notifier": ["simple-update-notifier@2.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w=="],
"supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"touch": ["touch@3.1.1", "", { "bin": { "nodetouch": "bin/nodetouch.js" } }, "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA=="],
"ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
"undefsafe": ["undefsafe@2.0.5", "", {}, "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA=="],
"undici": ["undici@6.21.3", "", {}, "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw=="],
"undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="],
"ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
"@discordjs/rest/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="],
"@discordjs/ws/@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="],
}
}

1
discord_bot/index.ts Normal file
View File

@@ -0,0 +1 @@
console.log("Hello via Bun!");

9
discord_bot/node.js Normal file
View File

@@ -0,0 +1,9 @@
import { Client, Events } from 'discord.js';
const client = new Client({ intents: [] });
client.once(Events.ClientReady, readyClient => {
console.log(`Ready! Logged in as ${readyClient.user.tag}`);
});
client.login("MTQyMDcyNzg0NjEwNzc0NjMwNQ.GEKESY.AXDX46SKpNplleXJU9jbmDXU0Dzlru7hf4rV-4");

19
discord_bot/package.json Normal file
View File

@@ -0,0 +1,19 @@
{
"name": "discord_bot",
"module": "index.ts",
"type": "module",
"private": true,
"devDependencies": {
"@types/bun": "latest"
},
"peerDependencies": {
"typescript": "^5"
},
"scripts": {
"dev": "nodemon node nodejs"
},
"dependencies": {
"discord.js": "^14.22.1",
"nodemon": "^3.1.10"
}
}

29
discord_bot/tsconfig.json Normal file
View File

@@ -0,0 +1,29 @@
{
"compilerOptions": {
// Environment setup & latest features
"lib": ["ESNext"],
"target": "ESNext",
"module": "Preserve",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250925100420 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE customer_advert_payment_register (id SERIAL NOT NULL, advert_id INT DEFAULT NULL, amount DOUBLE PRECISION NOT NULL, create_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, numero_remise VARCHAR(255) DEFAULT NULL, cheque_num VARCHAR(255) DEFAULT NULL, type VARCHAR(255) NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_673F7A3ED07ECCB6 ON customer_advert_payment_register (advert_id)');
$this->addSql('COMMENT ON COLUMN customer_advert_payment_register.create_at IS \'(DC2Type:datetime_immutable)\'');
$this->addSql('ALTER TABLE customer_advert_payment_register ADD CONSTRAINT FK_673F7A3ED07ECCB6 FOREIGN KEY (advert_id) REFERENCES customer_advert_payment (id) NOT DEFERRABLE INITIALLY IMMEDIATE');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('ALTER TABLE customer_advert_payment_register DROP CONSTRAINT FK_673F7A3ED07ECCB6');
$this->addSql('DROP TABLE customer_advert_payment_register');
}
}

BIN
public/sound/alert.mp3 Normal file

Binary file not shown.

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Command;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(name: 'mainframe:export')]
class ExportComptable extends Command
{
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$io->title('Export comptable');
$lines =[];
$lines[] = ['JournalCode','JournalLib','EcritureNum','EcritureDate','CompteNum','CompteLib','CompAuxNum','CompAuxLib','PieceRef','PieceDate','EcritureLib','Debit','Credit','EcritureLet','DateLet','ValidDate','Montantdevise','Idevise','DateRglt','ModeRglt','NatOp','IdClient'];
return Command::SUCCESS;
}
}

View File

@@ -9,6 +9,15 @@ use Symfony\Component\Routing\Attribute\Route;
class AvatarController extends AbstractController
{
#[Route(path: '/artemis/lockdown',name: 'artemis_lockdown',methods: ['GET', 'POST'])]
public function artemisLockdown(): Response
{
return $this->json([
'lockdown' => false
]);
}
#[Route(path: '/artemis/avatar',name: 'artemis_avatar',methods: ['GET', 'POST'])]
public function artemis(): Response
{

View File

@@ -43,9 +43,31 @@ use Symfony\Component\Uid\Uuid;
class CustomerController extends AbstractController
{
#[Route(path: '/artemis/intranet/customer',name: 'artemis_intranet_customer',methods: ['GET', 'POST'])]
public function customers(CustomerRepository $customerRepository,Request $request,PaginatorInterface $paginator): Response
public function customers(LoggerService $loggerService,CustomerRepository $customerRepository,EntityManagerInterface $entityManager,Request $request,PaginatorInterface $paginator): Response
{
if($request->query->has('idCopy')){
/** @var Customer $customer */
$customer = $customerRepository->find($request->query->get('idCopy'));
$newCustomer = clone $customer;
$entityManager->persist($newCustomer);
foreach ($customer->getCustomerContacts() as $customerContact) {
$contact = new CustomerContact();
$contact->setName($customerContact->getName());
$contact->setEmail($customerContact->getEmail());
$contact->setPhone($customerContact->getPhone());
$contact->setIsMain($customerContact->isMain());
$contact->setSurname($customerContact->getSurname());
$entityManager->persist($contact);
$newCustomer->addCustomerContact($contact);
}
$entityManager->persist($newCustomer);
$entityManager->flush();
$loggerService->log('CREATE',"Copie d'une fiche client - ".$newCustomer->getRaisonSocial(),$this->getUser());
$this->addFlash("success","Copie effectuée");
return $this->redirectToRoute('artemis_intranet_customer');
}
return $this->render('artemis/intranet/customer.twig',[
'customers' => $paginator->paginate($customerRepository->searchCustomer($request),$request->get('page',1),20),
]);
@@ -173,6 +195,10 @@ class CustomerController extends AbstractController
}
if($request->query->has('idDevis') && $request->query->has('act')) {
$devis = $entityManager->getRepository(CustomerDevis::class)->find($request->query->get('idDevis'));
$event = new CreateDevisCustomerEvent($devis,false);
$eventDispatcher->dispatch($event);
if($request->query->get('act') == "createAvisPayment") {
/** @var CustomerDevis $devis */
$devis = $entityManager->getRepository(CustomerDevis::class)->find($request->query->get('idDevis'));

View File

@@ -101,6 +101,20 @@ class Customer
#[ORM\Column(length: 255, nullable: true)]
private ?string $stancerId = null;
public function __clone(): void
{
$this->id = null;
$this->raisonSocial = "Copie - ".$this->raisonSocial;
$this->codeComptable = "";
$this->customerContacts = new ArrayCollection();
$this->customerDns = new ArrayCollection();
$this->customerDevis = new ArrayCollection();
$this->customerAdvertPayments = new ArrayCollection();
$this->customerOrders = new ArrayCollection();
$this->stancerId = null;
}
public function __construct()
{
$this->customerContacts = new ArrayCollection();
@@ -487,4 +501,9 @@ class Customer
return $this;
}
public function setId(null $null)
{
$this->id = $null;
}
}

View File

@@ -67,9 +67,16 @@ class CustomerAdvertPayment
#[ORM\Column(nullable: true)]
private ?\DateTimeImmutable $payAt = null;
/**
* @var Collection<int, CustomerAdvertPaymentRegister>
*/
#[ORM\OneToMany(targetEntity: CustomerAdvertPaymentRegister::class, mappedBy: 'advert')]
private Collection $customerAdvertPaymentRegisters;
public function __construct()
{
$this->customerAdvertPaymentLines = new ArrayCollection();
$this->customerAdvertPaymentRegisters = new ArrayCollection();
}
public function getId(): ?int
@@ -339,4 +346,34 @@ class CustomerAdvertPayment
return $this;
}
/**
* @return Collection<int, CustomerAdvertPaymentRegister>
*/
public function getCustomerAdvertPaymentRegisters(): Collection
{
return $this->customerAdvertPaymentRegisters;
}
public function addCustomerAdvertPaymentRegister(CustomerAdvertPaymentRegister $customerAdvertPaymentRegister): static
{
if (!$this->customerAdvertPaymentRegisters->contains($customerAdvertPaymentRegister)) {
$this->customerAdvertPaymentRegisters->add($customerAdvertPaymentRegister);
$customerAdvertPaymentRegister->setAdvert($this);
}
return $this;
}
public function removeCustomerAdvertPaymentRegister(CustomerAdvertPaymentRegister $customerAdvertPaymentRegister): static
{
if ($this->customerAdvertPaymentRegisters->removeElement($customerAdvertPaymentRegister)) {
// set the owning side to null (unless already changed)
if ($customerAdvertPaymentRegister->getAdvert() === $this) {
$customerAdvertPaymentRegister->setAdvert(null);
}
}
return $this;
}
}

View File

@@ -0,0 +1,116 @@
<?php
namespace App\Entity;
use App\Repository\CustomerAdvertPaymentRegisterRepository;
use Doctrine\DBAL\Types\DateImmutableType;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: CustomerAdvertPaymentRegisterRepository::class)]
class CustomerAdvertPaymentRegister
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\ManyToOne(inversedBy: 'customerAdvertPaymentRegisters')]
private ?CustomerAdvertPayment $advert = null;
#[ORM\Column]
private ?float $amount = null;
#[ORM\Column()]
private ?\DateTimeImmutable $createAt = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $numeroRemise = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $chequeNum = null;
#[ORM\Column(length: 255)]
private ?string $type = null;
public function getId(): ?int
{
return $this->id;
}
public function getAdvert(): ?CustomerAdvertPayment
{
return $this->advert;
}
public function setAdvert(?CustomerAdvertPayment $advert): static
{
$this->advert = $advert;
return $this;
}
public function getAmount(): ?float
{
return $this->amount;
}
public function setAmount(float $amount): static
{
$this->amount = $amount;
return $this;
}
/**
* @return \DateTimeImmutable|null
*/
public function getCreateAt(): ?\DateTimeImmutable
{
return $this->createAt;
}
/**
* @param \DateTimeImmutable|null $createAt
*/
public function setCreateAt(?\DateTimeImmutable $createAt): self
{
$this->createAt = $createAt;
return $this;
}
public function getNumeroRemise(): ?string
{
return $this->numeroRemise;
}
public function setNumeroRemise(?string $numeroRemise): static
{
$this->numeroRemise = $numeroRemise;
return $this;
}
public function getChequeNum(): ?string
{
return $this->chequeNum;
}
public function setChequeNum(?string $chequeNul): static
{
$this->chequeNum = $chequeNul;
return $this;
}
public function getType(): ?string
{
return $this->type;
}
public function setType(string $type): static
{
$this->type = $type;
return $this;
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Repository;
use App\Entity\CustomerAdvertPaymentRegister;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<CustomerAdvertPaymentRegister>
*/
class CustomerAdvertPaymentRegisterRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, CustomerAdvertPaymentRegister::class);
}
// /**
// * @return CustomerAdvertPaymentRegister[] Returns an array of CustomerAdvertPaymentRegister objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('c')
// ->andWhere('c.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('c.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?CustomerAdvertPaymentRegister
// {
// return $this->createQueryBuilder('c')
// ->andWhere('c.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

View File

@@ -4,13 +4,15 @@ namespace App\Service\Docuseal;
use App\Entity\CustomerDevis;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Vich\UploaderBundle\Templating\Helper\UploaderHelper;
class SignClient
{
private \Docuseal\Api $docuseal;
public function __construct(private readonly UrlGeneratorInterface $urlGenerator,private readonly EntityManagerInterface $entityManager)
public function __construct(private readonly RequestStack $requestStack,private readonly UploaderHelper $uploaderHelper,private readonly UrlGeneratorInterface $urlGenerator,private readonly EntityManagerInterface $entityManager)
{
$this->docuseal = new \Docuseal\Api($_ENV['DOCUSIGN_KEY'], $_ENV['DOCUSIGN_URL']);
@@ -21,53 +23,30 @@ class SignClient
$t = new \DateTimeImmutable();
if($devis->getDevisSubmiterId() == null) {
$submissionId = $this->docuseal->createSubmission([
'template_id' => 1,
'send_email' => false,
'completed_redirect_url' => $this->urlGenerator->generate('app_sign_complete',['type'=>'devis','id'=>$devis->getId()],UrlGeneratorInterface::ABSOLUTE_URL),
'submitters' => [
if($devis->getDevisSubmiterId() == null) {
$submissionId = $this->docuseal->createSubmissionFromPdf([
'name' => 'Devis N°'.$devis->getNumDevis(),
'completed_redirect_url' => $this->urlGenerator->generate('app_sign_complete',['type'=>'devis','id'=>$devis->getId()],UrlGeneratorInterface::ABSOLUTE_URL),
'send_email' => false,
'documents' => [
[
'name' => 'devis',
'file' => $this->requestStack->getCurrentRequest()->getSchemeAndHttpHost().$this->uploaderHelper->asset($devis,'devis'),
]
],
'submitters' => [
[
'role' => 'Client',
'role' => 'First Party',
'email' => $devis->getCustomer()->mainContact()->getEmail(),
'metadata' => [
'type' =>'devis',
'id' => $devis->getId(),
],
'fields' => [
[
'name' => 'Numéro devis',
'value' => $devis->getNumDevis(),
'readonly' => true,
],
[
'name' => 'Raison Social',
'value' => $devis->getCustomer()->getRaisonSocial(),
'readonly' => true,
],
[
'name' => 'Adresse',
'value' => $devis->getCustomer()->getAddress(),
'readonly' => true,
],
[
'name' => 'Email',
'value' => $devis->getCustomer()->mainContact()->getEmail(),
'readonly' => true,
],
[
'name' => 'Date Signature',
'value' => 'Signée le ' . $t->format('d/m/Y H:i:s'),
'readonly' => true,
]
]
]
],
]);
$devis->setDevisSubmiterId($submissionId['id']);
$this->entityManager->persist($devis);
$this->entityManager->flush();
$submissionData = $this->docuseal->getSubmitter($devis->getDevisSubmiterId());
]
]);
$devis->setDevisSubmiterId($submissionId['id']);
$this->entityManager->persist($devis);
$this->entityManager->flush();
$submissionData = $this->docuseal->getSubmitter($devis->getDevisSubmiterId());
} else {
$submissionData = $this->docuseal->getSubmitter($devis->getDevisSubmiterId());
}

View File

@@ -145,15 +145,16 @@ class DevisPdf extends FPDF
// Position the summary block at the bottom of the page
$this->SetY(-60);
$this->Cell(30,10,"{{Sign;type=signature;role=First Party}}", 0, 0, 'L');
// Display the summary
$this->SetFont('Arial', '', 12);
$this->Cell(135, 10, mb_convert_encoding('Total HT :', 'ISO-8859-1', 'UTF-8'), 0, 0, 'R');
$this->Cell(100, 10, mb_convert_encoding('Total HT :', 'ISO-8859-1', 'UTF-8'), 0, 0, 'R');
$this->Cell(40, 10, number_format($totalHT, 2, ",") . " " . EURO, 0, 1, 'R');
$this->Cell(135, 10, mb_convert_encoding('TVA (20%) :', 'ISO-8859-1', 'UTF-8'), 0, 0, 'R');
$this->Cell(40, 10, number_format($totalTVA, 2, ",") . " " . EURO, 0, 1, 'R');
$this->Cell(35, 10, number_format($totalTVA, 2, ",") . " " . EURO, 0, 1, 'R');
$this->SetFont('Arial', 'B', 12);
$this->Cell(135, 10, mb_convert_encoding('Total :', 'ISO-8859-1', 'UTF-8'), 0, 0, 'R');
$this->Cell(40, 10, number_format($totalTTC, 2, ",") . " " . EURO, 0, 1, 'R');
$this->Cell(35, 10, number_format($totalTTC, 2, ",") . " " . EURO, 0, 1, 'R');
}
}

View File

@@ -194,5 +194,8 @@
</div>
</div>
<security-wall></security-wall>
{#<ip-wall></ip-wall>#}
<lockdown-wall></lockdown-wall>
</body>
</html>

View File

@@ -62,6 +62,7 @@
</thead>
<tbody class="divide-y divide-gray-700">
{% for customer in customers %}
<tr class="hover:bg-gray-700 transition relative hover:bg-gray-700 transition">
<td class="px-6 py-4 whitespace-nowrap">
<div class="text-sm font-semibold">{{ customer.type|trans }} - {{ customer.raisonSocial }}</div>
@@ -70,11 +71,12 @@
<div class="text-xs text-gray-400">{{ customer.mainContact.name }} {{ customer.mainContact.surname }}</div>
</td>
<td class="px-6 py-4 text-center text-sm">0</td>
<td class="px-6 py-4 text-center text-sm">0</td>
<td class="px-6 py-4 text-center text-sm">{{ customer.customerDns.count }}</td>
<td class="px-6 py-4 text-center text-sm">0</td>
<td class="px-6 py-4 text-center text-sm">0</td>
<td class="px-6 py-4 text-center text-sm text-green-500 font-bold">Non</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-center">
<a href="{{ path('artemis_intranet_customer',{idCopy:customer.id}) }}" class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded mr-2">Copier le client</a>
<a href="{{ path('artemis_intranet_customer_view',{id:customer.id}) }}" class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded mr-2">Voir</a>
<a href="{{ path('artemis_intranet_customer_delete',{id:customer.id}) }}" class="bg-red-600 hover:bg-red-700 text-white px-3 py-1 rounded">Supprimer</a>
</td>