Compare commits
90 Commits
main
...
Feat/fourn
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18071dcae7 | ||
|
|
431d9731e6 | ||
|
|
cab2de09ff | ||
|
|
9440098815 | ||
|
|
c4977bad15 | ||
|
|
9951ed0ee6 | ||
|
|
67b15ab337 | ||
|
|
ae7574045e | ||
|
|
5c8779cb6a | ||
|
|
34875321ac | ||
|
|
1d18659bd7 | ||
|
|
3dcb88290d | ||
|
|
bc497382a3 | ||
|
|
e27cce45e0 | ||
|
|
d275c460b6 | ||
|
|
d5916d96a2 | ||
|
|
ce61b79080 | ||
|
|
d8d2b68421 | ||
|
|
3e6ac4055c | ||
|
|
284d228dc5 | ||
|
|
56b0c50111 | ||
|
|
8f7019e815 | ||
|
|
9cbc1bcbdb | ||
|
|
dd6fc4665c | ||
|
|
ebd171e9de | ||
|
|
8ee7d8f8e9 | ||
|
|
8171a20d41 | ||
|
|
bd04e07f12 | ||
|
|
dec87dfdb7 | ||
|
|
8074ac4f48 | ||
|
|
11750a3ffc | ||
|
|
dc87b0f720 | ||
|
|
ecfe25d3ca | ||
|
|
083f78673e | ||
|
|
a9a2429b67 | ||
|
|
094c7a0980 | ||
|
|
31090d12ba | ||
|
|
d8927580e7 | ||
|
|
4af8ea2c60 | ||
|
|
c0868b6acb | ||
|
|
5a2b1684aa | ||
|
|
44681da674 | ||
|
|
d57e9a1a67 | ||
|
|
ed5181d290 | ||
|
|
0009eb8c86 | ||
|
|
3bc4178a12 | ||
|
|
f62a2db36e | ||
|
|
86472e0de9 | ||
|
|
16a39014a2 | ||
|
|
39a3062009 | ||
|
|
c00ce5ab94 | ||
|
|
503fb0d008 | ||
|
|
e0ccd5f627 | ||
|
|
50f79a8040 | ||
|
|
d911435b5c | ||
|
|
19b592720e | ||
|
|
5d93f9d39a | ||
|
|
f9b6e5e0f6 | ||
|
|
23bce2abcf | ||
|
|
496b427e13 | ||
|
|
a51e05559a | ||
|
|
4b7e075918 | ||
|
|
98d1743def | ||
|
|
69fbe1a7a1 | ||
|
|
7570f46658 | ||
|
|
0c4ff92fd5 | ||
|
|
51ff282d5b | ||
|
|
bbf60fb380 | ||
|
|
8d1d65e27b | ||
|
|
e55cc5253e | ||
|
|
0ea8f1866b | ||
|
|
aa306f5d19 | ||
|
|
638af78e1f | ||
|
|
18f9d83e5a | ||
|
|
cfdbc11b1a | ||
|
|
edb9c87c1e | ||
|
|
4b056038d6 | ||
|
|
ca09f6da2f | ||
|
|
e924c4f819 | ||
|
|
425d2d510c | ||
|
|
ea2b687533 | ||
|
|
2a1de6f384 | ||
|
|
99d88ca30b | ||
|
|
b62cb3d717 | ||
|
|
78700a3c5a | ||
|
|
e2cb4499bb | ||
|
|
98420a29b5 | ||
|
|
c5a4fcc546 | ||
|
|
175446adbe | ||
|
|
215f4c4071 |
4
.gitignore
vendored
4
.gitignore
vendored
@ -20,7 +20,11 @@
|
||||
*.DS_Store
|
||||
*.idea/
|
||||
*.vscode/
|
||||
node_modules/
|
||||
|
||||
*.env.local
|
||||
*.github/
|
||||
*.opencode/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
27
.opencode/antigravity.json
Normal file
27
.opencode/antigravity.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/NoeFabris/opencode-antigravity-auth/main/assets/antigravity.schema.json",
|
||||
"quiet_mode": false,
|
||||
"debug": false,
|
||||
"auto_update": true,
|
||||
"keep_thinking": false,
|
||||
"session_recovery": true,
|
||||
"auto_resume": true,
|
||||
"resume_text": "continue",
|
||||
"empty_response_max_attempts": 4,
|
||||
"empty_response_retry_delay_ms": 2000,
|
||||
"tool_id_recovery": true,
|
||||
"claude_tool_hardening": true,
|
||||
"proactive_token_refresh": true,
|
||||
"proactive_refresh_buffer_seconds": 1800,
|
||||
"proactive_refresh_check_interval_seconds": 300,
|
||||
"max_rate_limit_wait_seconds": 300,
|
||||
"quota_fallback": false,
|
||||
"account_selection_strategy": "sticky",
|
||||
"pid_offset_enabled": false,
|
||||
"signature_cache": {
|
||||
"enabled": true,
|
||||
"memory_ttl_seconds": 3600,
|
||||
"disk_ttl_seconds": 172800,
|
||||
"write_interval_seconds": 60
|
||||
}
|
||||
}
|
||||
73
.opencode/opencode.json
Normal file
73
.opencode/opencode.json
Normal file
@ -0,0 +1,73 @@
|
||||
{
|
||||
"$schema": "https://opencode.ai/config.json",
|
||||
"plugin": ["opencode-antigravity-auth@beta"],
|
||||
"provider": {
|
||||
"google": {
|
||||
"models": {
|
||||
"antigravity-gemini-3-pro": {
|
||||
"name": "Gemini 3 Pro (Antigravity)",
|
||||
"limit": { "context": 1048576, "output": 65535 },
|
||||
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] },
|
||||
"variants": {
|
||||
"low": { "thinkingLevel": "low" },
|
||||
"high": { "thinkingLevel": "high" }
|
||||
}
|
||||
},
|
||||
"antigravity-gemini-3-flash": {
|
||||
"name": "Gemini 3 Flash (Antigravity)",
|
||||
"limit": { "context": 1048576, "output": 65536 },
|
||||
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] },
|
||||
"variants": {
|
||||
"minimal": { "thinkingLevel": "minimal" },
|
||||
"low": { "thinkingLevel": "low" },
|
||||
"medium": { "thinkingLevel": "medium" },
|
||||
"high": { "thinkingLevel": "high" }
|
||||
}
|
||||
},
|
||||
"antigravity-claude-sonnet-4-5": {
|
||||
"name": "Claude Sonnet 4.5 (Antigravity)",
|
||||
"limit": { "context": 200000, "output": 64000 },
|
||||
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
|
||||
},
|
||||
"antigravity-claude-sonnet-4-5-thinking": {
|
||||
"name": "Claude Sonnet 4.5 Thinking (Antigravity)",
|
||||
"limit": { "context": 200000, "output": 64000 },
|
||||
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] },
|
||||
"variants": {
|
||||
"low": { "thinkingConfig": { "thinkingBudget": 8192 } },
|
||||
"max": { "thinkingConfig": { "thinkingBudget": 32768 } }
|
||||
}
|
||||
},
|
||||
"antigravity-claude-opus-4-5-thinking": {
|
||||
"name": "Claude Opus 4.5 Thinking (Antigravity)",
|
||||
"limit": { "context": 200000, "output": 64000 },
|
||||
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] },
|
||||
"variants": {
|
||||
"low": { "thinkingConfig": { "thinkingBudget": 8192 } },
|
||||
"max": { "thinkingConfig": { "thinkingBudget": 32768 } }
|
||||
}
|
||||
},
|
||||
"gemini-2.5-flash": {
|
||||
"name": "Gemini 2.5 Flash (Gemini CLI)",
|
||||
"limit": { "context": 1048576, "output": 65536 },
|
||||
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
|
||||
},
|
||||
"gemini-2.5-pro": {
|
||||
"name": "Gemini 2.5 Pro (Gemini CLI)",
|
||||
"limit": { "context": 1048576, "output": 65536 },
|
||||
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
|
||||
},
|
||||
"gemini-3-flash-preview": {
|
||||
"name": "Gemini 3 Flash Preview (Gemini CLI)",
|
||||
"limit": { "context": 1048576, "output": 65536 },
|
||||
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
|
||||
},
|
||||
"gemini-3-pro-preview": {
|
||||
"name": "Gemini 3 Pro Preview (Gemini CLI)",
|
||||
"limit": { "context": 1048576, "output": 65535 },
|
||||
"modalities": { "input": ["text", "image", "pdf"], "output": ["text"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
389
.opencode/package-lock.json
generated
Normal file
389
.opencode/package-lock.json
generated
Normal file
@ -0,0 +1,389 @@
|
||||
{
|
||||
"name": ".opencode",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"@kilocode/plugin": "7.2.22",
|
||||
"@opencode-ai/plugin": "1.1.31"
|
||||
}
|
||||
},
|
||||
"node_modules/@kilocode/plugin": {
|
||||
"version": "7.2.22",
|
||||
"resolved": "https://registry.npmjs.org/@kilocode/plugin/-/plugin-7.2.22.tgz",
|
||||
"integrity": "sha512-uS8tnoLzXAyDHHgSOvP/GhrvkKpus6i6tmWb57E4+YfgHBOO7HqF+LzV4MiC1cuGIifbMtyUEi3kZWmWdIuhhw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@kilocode/sdk": "7.2.22",
|
||||
"effect": "4.0.0-beta.48",
|
||||
"zod": "4.1.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@opentui/core": ">=0.1.100",
|
||||
"@opentui/solid": ">=0.1.100"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@opentui/core": {
|
||||
"optional": true
|
||||
},
|
||||
"@opentui/solid": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@kilocode/sdk": {
|
||||
"version": "7.2.22",
|
||||
"resolved": "https://registry.npmjs.org/@kilocode/sdk/-/sdk-7.2.22.tgz",
|
||||
"integrity": "sha512-2t4VuK5rVY9o/Pck/oRJ+CxAAqnwLhRAD/i91uSabWw4POGlOHHsq2etQKFAX8kJ5zdTk/I1DLvffh7bFPPXZw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cross-spawn": "7.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz",
|
||||
"integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz",
|
||||
"integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz",
|
||||
"integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz",
|
||||
"integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz",
|
||||
"integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz",
|
||||
"integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@opencode-ai/plugin": {
|
||||
"version": "1.1.31",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@opencode-ai/sdk": "1.1.31",
|
||||
"zod": "4.1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@opencode-ai/sdk": {
|
||||
"version": "1.1.31",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@standard-schema/spec": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
|
||||
"integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
||||
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/effect": {
|
||||
"version": "4.0.0-beta.48",
|
||||
"resolved": "https://registry.npmjs.org/effect/-/effect-4.0.0-beta.48.tgz",
|
||||
"integrity": "sha512-MMAM/ZabuNdNmgXiin+BAanQXK7qM8mlt7nfXDoJ/Gn9V8i89JlCq+2N0AiWmqFLXjGLA0u3FjiOjSOYQk5uMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.1.0",
|
||||
"fast-check": "^4.6.0",
|
||||
"find-my-way-ts": "^0.1.6",
|
||||
"ini": "^6.0.0",
|
||||
"kubernetes-types": "^1.30.0",
|
||||
"msgpackr": "^1.11.9",
|
||||
"multipasta": "^0.2.7",
|
||||
"toml": "^4.1.1",
|
||||
"uuid": "^13.0.0",
|
||||
"yaml": "^2.8.3"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-check": {
|
||||
"version": "4.7.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-check/-/fast-check-4.7.0.tgz",
|
||||
"integrity": "sha512-NsZRtqvSSoCP0HbNjUD+r1JH8zqZalyp6gLY9e7OYs7NK9b6AHOs2baBFeBG7bVNsuoukh89x2Yg3rPsul8ziQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/dubzzz"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fast-check"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"pure-rand": "^8.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/find-my-way-ts": {
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/find-my-way-ts/-/find-my-way-ts-0.1.6.tgz",
|
||||
"integrity": "sha512-a85L9ZoXtNAey3Y6Z+eBWW658kO/MwR7zIafkIUPUMf3isZG0NCs2pjW2wtjxAKuJPxMAsHUIP4ZPGv0o5gyTA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ini": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz",
|
||||
"integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "^20.17.0 || >=22.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/kubernetes-types": {
|
||||
"version": "1.30.0",
|
||||
"resolved": "https://registry.npmjs.org/kubernetes-types/-/kubernetes-types-1.30.0.tgz",
|
||||
"integrity": "sha512-Dew1okvhM/SQcIa2rcgujNndZwU8VnSapDgdxlYoB84ZlpAD43U6KLAFqYo17ykSFGHNPrg0qry0bP+GJd9v7Q==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/msgpackr": {
|
||||
"version": "1.11.10",
|
||||
"resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.10.tgz",
|
||||
"integrity": "sha512-iCZNq+HszvF+fC3anCm4nBmWEnbeIAfpDs6IStAEKhQ2YSgkjzVG2FF9XJqwwQh5bH3N9OUTUt4QwVN6MLMLtA==",
|
||||
"license": "MIT",
|
||||
"optionalDependencies": {
|
||||
"msgpackr-extract": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/msgpackr-extract": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.3.tgz",
|
||||
"integrity": "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"node-gyp-build-optional-packages": "5.2.2"
|
||||
},
|
||||
"bin": {
|
||||
"download-msgpackr-prebuilds": "bin/download-prebuilds.js"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3",
|
||||
"@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3",
|
||||
"@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3",
|
||||
"@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3",
|
||||
"@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3",
|
||||
"@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/multipasta": {
|
||||
"version": "0.2.7",
|
||||
"resolved": "https://registry.npmjs.org/multipasta/-/multipasta-0.2.7.tgz",
|
||||
"integrity": "sha512-KPA58d68KgGil15oDqXjkUBEBYc00XvbPj5/X+dyzeo/lWm9Nc25pQRlf1D+gv4OpK7NM0J1odrbu9JNNGvynA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-gyp-build-optional-packages": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz",
|
||||
"integrity": "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"node-gyp-build-optional-packages": "bin.js",
|
||||
"node-gyp-build-optional-packages-optional": "optional.js",
|
||||
"node-gyp-build-optional-packages-test": "build-test.js"
|
||||
}
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/pure-rand": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-8.4.0.tgz",
|
||||
"integrity": "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/dubzzz"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fast-check"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/toml": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/toml/-/toml-4.1.1.tgz",
|
||||
"integrity": "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "13.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz",
|
||||
"integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==",
|
||||
"funding": [
|
||||
"https://github.com/sponsors/broofa",
|
||||
"https://github.com/sponsors/ctavan"
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist-node/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"node-which": "bin/node-which"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.8.3",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
|
||||
"integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/eemeli"
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "4.1.8",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
291
Convoi.json
Normal file
291
Convoi.json
Normal file
@ -0,0 +1,291 @@
|
||||
{
|
||||
"defuntId": null,
|
||||
"titreMission": "",
|
||||
"clientId": null,
|
||||
"typeConvoi": "local",
|
||||
"lieuDepart": {
|
||||
"modeSelection": "lieu",
|
||||
"lieuId": null,
|
||||
"nom": "",
|
||||
"adresse": "",
|
||||
"ville": "",
|
||||
"codePostal": "",
|
||||
"pays": "",
|
||||
"latitude": null,
|
||||
"longitude": null,
|
||||
"detailsComplementaires": ""
|
||||
},
|
||||
"modeTransport": "route",
|
||||
"debutPrevu": "2026-04-15T10:57",
|
||||
"finEstimee": null,
|
||||
"statut": "planifie",
|
||||
"emailFamille": "",
|
||||
"notificationsAutomatiques": true,
|
||||
"ongletsMission": {
|
||||
"itineraire": {
|
||||
"enabled": false
|
||||
},
|
||||
"documentsLegaux": {
|
||||
"enabled": false
|
||||
},
|
||||
"equipeMoyens": {
|
||||
"enabled": true
|
||||
},
|
||||
"demarches": {
|
||||
"enabled": true
|
||||
},
|
||||
"suiviCouts": {
|
||||
"enabled": true
|
||||
},
|
||||
"carburant": {
|
||||
"enabled": true
|
||||
},
|
||||
"ceremonie": {
|
||||
"enabled": true
|
||||
},
|
||||
"thanatopraxie": {
|
||||
"enabled": true
|
||||
},
|
||||
"suiviGpsEtapes": {
|
||||
"enabled": false
|
||||
},
|
||||
"communication": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// VEHICULE
|
||||
{
|
||||
"attributMissionConvoi": {
|
||||
"defuntId": null,
|
||||
"titreMission": "",
|
||||
"clientId": null,
|
||||
"typeConvoi": "local",
|
||||
"lieuDepart": {
|
||||
"modeSelection": "lieu",
|
||||
"lieuId": null,
|
||||
"nom": "",
|
||||
"adresse": "",
|
||||
"ville": "",
|
||||
"codePostal": "",
|
||||
"pays": "",
|
||||
"latitude": null,
|
||||
"longitude": null,
|
||||
"detailsComplementaires": ""
|
||||
},
|
||||
"modeTransport": "route",
|
||||
"debutPrevu": "2026-04-15T10:57",
|
||||
"finEstimee": null,
|
||||
"statut": "planifie",
|
||||
"emailFamille": "",
|
||||
"notificationsAutomatiques": true,
|
||||
"ongletsMission": {
|
||||
"itineraire": false,
|
||||
"documentsLegaux": false,
|
||||
"equipeMoyens": true,
|
||||
"demarches": true,
|
||||
"suiviCouts": true,
|
||||
"carburant": true,
|
||||
"ceremonie": true,
|
||||
"thanatopraxie": true,
|
||||
"suiviGpsEtapes": false,
|
||||
"communication": true
|
||||
}
|
||||
},
|
||||
"attributVehicule": {
|
||||
"photoVehicule": {
|
||||
"fileName": "",
|
||||
"fileUrl": "",
|
||||
"mimeType": "",
|
||||
"size": null
|
||||
},
|
||||
"marque": "",
|
||||
"modele": "",
|
||||
"immatriculation": "",
|
||||
"typeVehicule": "utilitaire",
|
||||
"carburant": "diesel",
|
||||
"annee": 2026,
|
||||
"utilisateurPrincipalId": null,
|
||||
"statut": "actif",
|
||||
"notes": "",
|
||||
"ongletsVehicule": {
|
||||
"informationsGenerales": true,
|
||||
"entretienMaintenance": true,
|
||||
"coutsAcquisition": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"attributMissionConvoi": {
|
||||
"defunt": {
|
||||
"label": "Défunt",
|
||||
"required": true,
|
||||
"type": "select",
|
||||
"value": null
|
||||
},
|
||||
"titreMission": {
|
||||
"label": "Titre de la mission",
|
||||
"required": false,
|
||||
"type": "string",
|
||||
"value": ""
|
||||
},
|
||||
"client": {
|
||||
"label": "Client (Donneur d'ordre)",
|
||||
"required": false,
|
||||
"type": "select",
|
||||
"value": null
|
||||
},
|
||||
"typeConvoi": {
|
||||
"label": "Type de convoi",
|
||||
"required": false,
|
||||
"type": "select",
|
||||
"value": "Local"
|
||||
},
|
||||
"lieuDepartConvoi": {
|
||||
"label": "Lieu de Départ du Convoi",
|
||||
"required": false,
|
||||
"type": "object",
|
||||
"value": {
|
||||
"mode": "selection_lieu",
|
||||
"recherche": "",
|
||||
"lieuId": null,
|
||||
"adresseManuelle": null
|
||||
}
|
||||
},
|
||||
"modeTransport": {
|
||||
"label": "Mode de Transport",
|
||||
"required": false,
|
||||
"type": "select",
|
||||
"value": "Route"
|
||||
},
|
||||
"debutPrevu": {
|
||||
"label": "Début Prévu",
|
||||
"required": true,
|
||||
"type": "datetime-local",
|
||||
"value": "2026-04-15T10:57"
|
||||
},
|
||||
"finEstimee": {
|
||||
"label": "Fin Estimée",
|
||||
"required": false,
|
||||
"type": "datetime-local",
|
||||
"value": null
|
||||
},
|
||||
"statut": {
|
||||
"label": "Statut",
|
||||
"required": false,
|
||||
"type": "select",
|
||||
"value": "Planifié"
|
||||
},
|
||||
"emailFamille": {
|
||||
"label": "Email famille (pour notifications auto)",
|
||||
"required": false,
|
||||
"type": "email",
|
||||
"value": ""
|
||||
},
|
||||
"notificationsAutomatiques": {
|
||||
"label": "Notifications automatiques (départ, arrivée, frontière)",
|
||||
"required": false,
|
||||
"type": "boolean",
|
||||
"value": true
|
||||
}
|
||||
},
|
||||
"attributVehicule": {
|
||||
"photoVehicule": {
|
||||
"label": "Photo du véhicule",
|
||||
"required": false,
|
||||
"type": "file:image",
|
||||
"value": null
|
||||
},
|
||||
"marque": {
|
||||
"label": "Marque",
|
||||
"required": true,
|
||||
"type": "select",
|
||||
"value": null,
|
||||
"options": [
|
||||
"Mercedes-Benz",
|
||||
"Peugeot",
|
||||
"Renault",
|
||||
"Citroën",
|
||||
"Volkswagen",
|
||||
"Ford",
|
||||
"Fiat",
|
||||
"Opel",
|
||||
"Toyota",
|
||||
"Nissan",
|
||||
"Volvo",
|
||||
"BMW",
|
||||
"Audi",
|
||||
"Iveco",
|
||||
"Autre"
|
||||
]
|
||||
},
|
||||
"modele": {
|
||||
"label": "Modèle",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
"value": ""
|
||||
},
|
||||
"immatriculation": {
|
||||
"label": "Immatriculation",
|
||||
"required": true,
|
||||
"type": "string",
|
||||
"value": ""
|
||||
},
|
||||
"typeVehicule": {
|
||||
"label": "Type véhicule",
|
||||
"required": false,
|
||||
"type": "select",
|
||||
"value": "utilitaire",
|
||||
"options": [
|
||||
"corbillard",
|
||||
"vehicule_transport",
|
||||
"utilitaire",
|
||||
"berline"
|
||||
]
|
||||
},
|
||||
"carburant": {
|
||||
"label": "Carburant",
|
||||
"required": false,
|
||||
"type": "select",
|
||||
"value": "diesel",
|
||||
"options": [
|
||||
"diesel",
|
||||
"essence",
|
||||
"electrique",
|
||||
"hybride"
|
||||
]
|
||||
},
|
||||
"annee": {
|
||||
"label": "Année",
|
||||
"required": false,
|
||||
"type": "number",
|
||||
"value": 2026
|
||||
},
|
||||
"utilisateurPrincipal": {
|
||||
"label": "Utilisateur principal",
|
||||
"required": false,
|
||||
"type": "select",
|
||||
"value": null
|
||||
},
|
||||
"statutVehicule": {
|
||||
"label": "Statut",
|
||||
"required": false,
|
||||
"type": "select",
|
||||
"value": "actif",
|
||||
"options": [
|
||||
"actif",
|
||||
"en_maintenance",
|
||||
"hors_service"
|
||||
]
|
||||
},
|
||||
"notes": {
|
||||
"label": "Notes",
|
||||
"required": false,
|
||||
"type": "textarea",
|
||||
"value": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
New-Thanasoft_MVP_Tracking.xlsx
Normal file
BIN
New-Thanasoft_MVP_Tracking.xlsx
Normal file
Binary file not shown.
1000
package-lock.json
generated
Normal file
1000
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
5
package.json
Normal file
5
package.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"exceljs": "^4.4.0"
|
||||
}
|
||||
}
|
||||
@ -1,65 +0,0 @@
|
||||
APP_NAME=Laravel
|
||||
APP_ENV=local
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
|
||||
APP_LOCALE=en
|
||||
APP_FALLBACK_LOCALE=en
|
||||
APP_FAKER_LOCALE=en_US
|
||||
|
||||
APP_MAINTENANCE_DRIVER=file
|
||||
# APP_MAINTENANCE_STORE=database
|
||||
|
||||
PHP_CLI_SERVER_WORKERS=4
|
||||
|
||||
BCRYPT_ROUNDS=12
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_STACK=single
|
||||
LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=debug
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=thanasoft_back
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=
|
||||
|
||||
SESSION_DRIVER=database
|
||||
SESSION_LIFETIME=120
|
||||
SESSION_ENCRYPT=false
|
||||
SESSION_PATH=/
|
||||
SESSION_DOMAIN=null
|
||||
|
||||
BROADCAST_CONNECTION=log
|
||||
FILESYSTEM_DISK=local
|
||||
QUEUE_CONNECTION=database
|
||||
|
||||
CACHE_STORE=database
|
||||
# CACHE_PREFIX=
|
||||
|
||||
MEMCACHED_HOST=127.0.0.1
|
||||
|
||||
REDIS_CLIENT=phpredis
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
MAIL_MAILER=log
|
||||
MAIL_SCHEME=null
|
||||
MAIL_HOST=127.0.0.1
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=null
|
||||
MAIL_PASSWORD=null
|
||||
MAIL_FROM_ADDRESS="hello@example.com"
|
||||
MAIL_FROM_NAME="${APP_NAME}"
|
||||
|
||||
AWS_ACCESS_KEY_ID=
|
||||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_DEFAULT_REGION=us-east-1
|
||||
AWS_BUCKET=
|
||||
AWS_USE_PATH_STYLE_ENDPOINT=false
|
||||
|
||||
VITE_APP_NAME="${APP_NAME}"
|
||||
656
thanasoft-back/API_DOCUMENTATION.md
Normal file
656
thanasoft-back/API_DOCUMENTATION.md
Normal file
@ -0,0 +1,656 @@
|
||||
# API Documentation - Employee Management System
|
||||
|
||||
## Overview
|
||||
|
||||
The ThanaSoft Employee Management System provides comprehensive RESTful API endpoints for managing employees, thanatopractitioners, and their associated documents. This system is built using Laravel and follows RESTful API conventions.
|
||||
|
||||
## Base URL
|
||||
|
||||
```
|
||||
https://your-domain.com/api
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
All API endpoints require authentication using Laravel Sanctum. Include the token in the Authorization header:
|
||||
|
||||
```
|
||||
Authorization: Bearer {your_token}
|
||||
```
|
||||
|
||||
## Employee Management System
|
||||
|
||||
### Entities
|
||||
|
||||
1. **Employees** - Core employee records with personal information
|
||||
2. **Thanatopractitioners** - Specialized practitioners linked to employees
|
||||
3. **Practitioner Documents** - Documents associated with thanatopractitioners
|
||||
|
||||
---
|
||||
|
||||
## Employees API
|
||||
|
||||
### Base Endpoint
|
||||
|
||||
```
|
||||
/employees
|
||||
```
|
||||
|
||||
### Endpoints Overview
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
| ------ | --------------------------------- | ----------------------------------------------- |
|
||||
| GET | `/employees` | List all employees with pagination |
|
||||
| POST | `/employees` | Create a new employee |
|
||||
| GET | `/employees/{id}` | Get specific employee details |
|
||||
| PUT | `/employees/{id}` | Update employee information |
|
||||
| DELETE | `/employees/{id}` | Delete an employee |
|
||||
| GET | `/employees/searchBy` | Search employees by criteria |
|
||||
| GET | `/employees/thanatopractitioners` | Get all thanatopractitioners with employee info |
|
||||
|
||||
### 1. List All Employees
|
||||
|
||||
**GET** `/api/employees`
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
- `page` (optional): Page number for pagination (default: 1)
|
||||
- `per_page` (optional): Items per page (default: 15, max: 100)
|
||||
- `search` (optional): Search term for name, email, or employee_number
|
||||
- `department` (optional): Filter by department
|
||||
- `status` (optional): Filter by employment status (active, inactive, terminated)
|
||||
|
||||
**Success Response (200):**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"employee_number": "EMP001",
|
||||
"first_name": "Jean",
|
||||
"last_name": "Dupont",
|
||||
"email": "jean.dupont@thanasoft.com",
|
||||
"phone": "+261 34 12 345 67",
|
||||
"department": "Direction",
|
||||
"position": "Directeur Général",
|
||||
"hire_date": "2020-01-15",
|
||||
"status": "active",
|
||||
"created_at": "2025-11-05T10:30:00.000000Z",
|
||||
"updated_at": "2025-11-05T10:30:00.000000Z"
|
||||
}
|
||||
],
|
||||
"current_page": 1,
|
||||
"per_page": 15,
|
||||
"total": 25,
|
||||
"last_page": 2
|
||||
},
|
||||
"message": "Employés récupérés avec succès"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Create Employee
|
||||
|
||||
**POST** `/api/employees`
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"employee_number": "EMP026",
|
||||
"first_name": "Jean",
|
||||
"last_name": "Dupont",
|
||||
"email": "jean.dupont@thanasoft.com",
|
||||
"phone": "+261 34 12 345 67",
|
||||
"address": "123 Rue de la Liberté, Antananarivo",
|
||||
"birth_date": "1985-06-15",
|
||||
"gender": "male",
|
||||
"department": "Direction",
|
||||
"position": "Directeur Général",
|
||||
"hire_date": "2025-11-05",
|
||||
"salary": 2500000,
|
||||
"status": "active"
|
||||
}
|
||||
```
|
||||
|
||||
**Success Response (201):**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": 26,
|
||||
"employee_number": "EMP026",
|
||||
"first_name": "Jean",
|
||||
"last_name": "Dupont",
|
||||
"email": "jean.dupont@thanasoft.com",
|
||||
"phone": "+261 34 12 345 67",
|
||||
"address": "123 Rue de la Liberté, Antananarivo",
|
||||
"birth_date": "1985-06-15",
|
||||
"gender": "male",
|
||||
"department": "Direction",
|
||||
"position": "Directeur Général",
|
||||
"hire_date": "2025-11-05",
|
||||
"salary": 2500000,
|
||||
"status": "active",
|
||||
"created_at": "2025-11-05T12:16:05.000000Z",
|
||||
"updated_at": "2025-11-05T12:16:05.000000Z"
|
||||
},
|
||||
"message": "Employé créé avec succès"
|
||||
}
|
||||
```
|
||||
|
||||
**Validation Errors (422):**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Les données fournies ne sont pas valides",
|
||||
"errors": {
|
||||
"email": ["L'adresse email doit être unique"],
|
||||
"employee_number": ["Le numéro d'employé est requis"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Get Employee Details
|
||||
|
||||
**GET** `/api/employees/{id}`
|
||||
|
||||
**Success Response (200):**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": 1,
|
||||
"employee_number": "EMP001",
|
||||
"first_name": "Jean",
|
||||
"last_name": "Dupont",
|
||||
"email": "jean.dupont@thanasoft.com",
|
||||
"phone": "+261 34 12 345 67",
|
||||
"address": "123 Rue de la Liberté, Antananarivo",
|
||||
"birth_date": "1985-06-15",
|
||||
"gender": "male",
|
||||
"department": "Direction",
|
||||
"position": "Directeur Général",
|
||||
"hire_date": "2020-01-15",
|
||||
"salary": 2500000,
|
||||
"status": "active",
|
||||
"created_at": "2020-01-15T08:00:00.000000Z",
|
||||
"updated_at": "2025-11-05T10:30:00.000000Z"
|
||||
},
|
||||
"message": "Détails de l'employé récupérés avec succès"
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Update Employee
|
||||
|
||||
**PUT** `/api/employees/{id}`
|
||||
|
||||
**Request Body:** Same as create, but all fields are optional.
|
||||
|
||||
**Success Response (200):**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": 1,
|
||||
"employee_number": "EMP001",
|
||||
"first_name": "Jean",
|
||||
"last_name": "Dupont",
|
||||
"email": "jean.dupont@thanasoft.com",
|
||||
"phone": "+261 34 12 345 68",
|
||||
"department": "Direction",
|
||||
"position": "Directeur Général Adjoint",
|
||||
"updated_at": "2025-11-05T12:16:05.000000Z"
|
||||
},
|
||||
"message": "Employé mis à jour avec succès"
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Search Employees
|
||||
|
||||
**GET** `/api/employees/searchBy`
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
- `query` (required): Search term
|
||||
- `field` (optional): Field to search in (first_name, last_name, email, employee_number, department, position)
|
||||
|
||||
**Example:** `/api/employees/searchBy?query=jean&field=first_name`
|
||||
|
||||
**Success Response (200):**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"employee_number": "EMP001",
|
||||
"first_name": "Jean",
|
||||
"last_name": "Dupont",
|
||||
"email": "jean.dupont@thanasoft.com",
|
||||
"department": "Direction",
|
||||
"position": "Directeur Général"
|
||||
}
|
||||
],
|
||||
"message": "Résultats de recherche récupérés avec succès"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Thanatopractitioners API
|
||||
|
||||
### Base Endpoint
|
||||
|
||||
```
|
||||
/thanatopractitioners
|
||||
```
|
||||
|
||||
### Endpoints Overview
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
| ------ | ---------------------------------------------- | ------------------------------------ |
|
||||
| GET | `/thanatopractitioners` | List all thanatopractitioners |
|
||||
| POST | `/thanatopractitioners` | Create a new thanatopractitioner |
|
||||
| GET | `/thanatopractitioners/{id}` | Get specific thanatopractitioner |
|
||||
| PUT | `/thanatopractitioners/{id}` | Update thanatopractitioner |
|
||||
| DELETE | `/thanatopractitioners/{id}` | Delete thanatopractitioner |
|
||||
| GET | `/employees/{employeeId}/thanatopractitioners` | Get thanatopractitioners by employee |
|
||||
|
||||
### 1. List All Thanatopractitioners
|
||||
|
||||
**GET** `/api/thanatopractitioners`
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
- `page` (optional): Page number
|
||||
- `per_page` (optional): Items per page
|
||||
- `specialization` (optional): Filter by specialization
|
||||
|
||||
**Success Response (200):**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"employee_id": 1,
|
||||
"specialization": "Thanatopraticien principal",
|
||||
"license_number": "TH001",
|
||||
"authorization_number": "AUTH001",
|
||||
"authorization_date": "2020-01-15",
|
||||
"authorization_expiry": "2025-01-15",
|
||||
"is_authorized": true,
|
||||
"created_at": "2020-01-15T08:00:00.000000Z",
|
||||
"updated_at": "2025-11-05T10:30:00.000000Z",
|
||||
"employee": {
|
||||
"id": 1,
|
||||
"first_name": "Jean",
|
||||
"last_name": "Dupont",
|
||||
"employee_number": "EMP001",
|
||||
"department": "Direction"
|
||||
}
|
||||
}
|
||||
],
|
||||
"current_page": 1,
|
||||
"per_page": 15,
|
||||
"total": 5,
|
||||
"last_page": 1
|
||||
},
|
||||
"message": "Thanatopraticiens récupérés avec succès"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Create Thanatopractitioner
|
||||
|
||||
**POST** `/api/thanatopractitioners`
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"employee_id": 1,
|
||||
"specialization": "Thanatopraticien principal",
|
||||
"license_number": "TH001",
|
||||
"authorization_number": "AUTH001",
|
||||
"authorization_date": "2025-11-05",
|
||||
"authorization_expiry": "2026-11-05",
|
||||
"is_authorized": true
|
||||
}
|
||||
```
|
||||
|
||||
**Success Response (201):**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": 6,
|
||||
"employee_id": 1,
|
||||
"specialization": "Thanatopraticien principal",
|
||||
"license_number": "TH001",
|
||||
"authorization_number": "AUTH001",
|
||||
"authorization_date": "2025-11-05",
|
||||
"authorization_expiry": "2026-11-05",
|
||||
"is_authorized": true,
|
||||
"created_at": "2025-11-05T12:16:05.000000Z",
|
||||
"updated_at": "2025-11-05T12:16:05.000000Z"
|
||||
},
|
||||
"message": "Thanatopraticien créé avec succès"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Get Thanatopractitioners by Employee
|
||||
|
||||
**GET** `/api/employees/{employeeId}/thanatopractitioners`
|
||||
|
||||
**Success Response (200):**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"specialization": "Thanatopraticien principal",
|
||||
"license_number": "TH001",
|
||||
"authorization_number": "AUTH001",
|
||||
"authorization_date": "2020-01-15",
|
||||
"authorization_expiry": "2025-01-15",
|
||||
"is_authorized": true
|
||||
}
|
||||
],
|
||||
"message": "Thanatopraticiens de l'employé récupérés avec succès"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Practitioner Documents API
|
||||
|
||||
### Base Endpoint
|
||||
|
||||
```
|
||||
/practitioner-documents
|
||||
```
|
||||
|
||||
### Endpoints Overview
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
| ------ | -------------------------------------- | ------------------------------------ |
|
||||
| GET | `/practitioner-documents` | List all documents |
|
||||
| POST | `/practitioner-documents` | Upload new document |
|
||||
| GET | `/practitioner-documents/{id}` | Get specific document |
|
||||
| PUT | `/practitioner-documents/{id}` | Update document info |
|
||||
| DELETE | `/practitioner-documents/{id}` | Delete document |
|
||||
| GET | `/practitioner-documents/searchBy` | Search documents |
|
||||
| GET | `/practitioner-documents/expiring` | Get expiring documents |
|
||||
| GET | `/thanatopractitioners/{id}/documents` | Get documents by thanatopractitioner |
|
||||
| PATCH | `/practitioner-documents/{id}/verify` | Verify document |
|
||||
|
||||
### 1. List All Documents
|
||||
|
||||
**GET** `/api/practitioner-documents`
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
- `page` (optional): Page number
|
||||
- `per_page` (optional): Items per page
|
||||
- `type` (optional): Filter by document type
|
||||
- `is_verified` (optional): Filter by verification status
|
||||
|
||||
**Success Response (200):**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"thanatopractitioner_id": 1,
|
||||
"document_type": "diplome",
|
||||
"file_name": "diplome_thanatopraticien.pdf",
|
||||
"file_path": "/documents/diplome_thanatopraticien.pdf",
|
||||
"issue_date": "2019-06-30",
|
||||
"expiry_date": "2024-06-30",
|
||||
"issuing_authority": "Ministère de la Santé",
|
||||
"is_verified": true,
|
||||
"verified_at": "2020-01-20T10:00:00.000000Z",
|
||||
"created_at": "2020-01-15T08:00:00.000000Z",
|
||||
"updated_at": "2020-01-20T10:00:00.000000Z"
|
||||
}
|
||||
],
|
||||
"current_page": 1,
|
||||
"per_page": 15,
|
||||
"total": 12,
|
||||
"last_page": 1
|
||||
},
|
||||
"message": "Documents récupérés avec succès"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Upload Document
|
||||
|
||||
**POST** `/api/practitioner-documents`
|
||||
|
||||
**Form Data:**
|
||||
|
||||
```
|
||||
thanatopractitioner_id: 1
|
||||
document_type: diplome
|
||||
file: [binary file data]
|
||||
issue_date: 2019-06-30
|
||||
expiry_date: 2024-06-30
|
||||
issuing_authority: Ministère de la Santé
|
||||
```
|
||||
|
||||
**Success Response (201):**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": 13,
|
||||
"thanatopractitioner_id": 1,
|
||||
"document_type": "diplome",
|
||||
"file_name": "diplome_thanatopraticien_2025.pdf",
|
||||
"file_path": "/documents/diplome_thanatopraticien_2025.pdf",
|
||||
"issue_date": "2019-06-30",
|
||||
"expiry_date": "2024-06-30",
|
||||
"issuing_authority": "Ministère de la Santé",
|
||||
"is_verified": false,
|
||||
"created_at": "2025-11-05T12:16:05.000000Z",
|
||||
"updated_at": "2025-11-05T12:16:05.000000Z"
|
||||
},
|
||||
"message": "Document téléchargé avec succès"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Get Expiring Documents
|
||||
|
||||
**GET** `/api/practitioner-documents/expiring`
|
||||
|
||||
**Query Parameters:**
|
||||
|
||||
- `days` (optional): Number of days to look ahead (default: 30)
|
||||
|
||||
**Success Response (200):**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"id": 5,
|
||||
"thanatopractitioner_id": 2,
|
||||
"document_type": "certificat",
|
||||
"file_name": "certificat_renouvellement.pdf",
|
||||
"expiry_date": "2025-11-20",
|
||||
"days_until_expiry": 15,
|
||||
"employee": {
|
||||
"first_name": "Marie",
|
||||
"last_name": "Rasoa",
|
||||
"employee_number": "EMP002"
|
||||
}
|
||||
}
|
||||
],
|
||||
"message": "Documents expirants récupérés avec succès"
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Verify Document
|
||||
|
||||
**PATCH** `/api/practitioner-documents/{id}/verify`
|
||||
|
||||
**Success Response (200):**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": 13,
|
||||
"document_type": "diplome",
|
||||
"is_verified": true,
|
||||
"verified_at": "2025-11-05T12:20:00.000000Z"
|
||||
},
|
||||
"message": "Document vérifié avec succès"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Models
|
||||
|
||||
### Employee Model
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| ----------------- | ------- | -------- | ------------------------------------------------ |
|
||||
| `employee_number` | string | Yes | Unique employee identifier |
|
||||
| `first_name` | string | Yes | Employee's first name |
|
||||
| `last_name` | string | Yes | Employee's last name |
|
||||
| `email` | email | Yes | Unique email address |
|
||||
| `phone` | string | No | Phone number |
|
||||
| `address` | text | No | Physical address |
|
||||
| `birth_date` | date | No | Date of birth |
|
||||
| `gender` | string | No | Gender (male, female, other) |
|
||||
| `department` | string | No | Department name |
|
||||
| `position` | string | No | Job position |
|
||||
| `hire_date` | date | No | Employment start date |
|
||||
| `salary` | decimal | No | Monthly salary |
|
||||
| `status` | string | No | Employment status (active, inactive, terminated) |
|
||||
|
||||
### Thanatopractitioner Model
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| ---------------------- | ------- | -------- | ------------------------------ |
|
||||
| `employee_id` | integer | Yes | Foreign key to employees table |
|
||||
| `specialization` | string | Yes | Area of specialization |
|
||||
| `license_number` | string | Yes | Professional license number |
|
||||
| `authorization_number` | string | Yes | Authorization number |
|
||||
| `authorization_date` | date | Yes | Authorization issue date |
|
||||
| `authorization_expiry` | date | Yes | Authorization expiry date |
|
||||
| `is_authorized` | boolean | Yes | Authorization status |
|
||||
|
||||
### Practitioner Document Model
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| ------------------------ | --------- | -------- | ----------------------------------------- |
|
||||
| `thanatopractitioner_id` | integer | Yes | Foreign key to thanatopractitioners table |
|
||||
| `document_type` | string | Yes | Type of document |
|
||||
| `file` | file | Yes | Document file upload |
|
||||
| `issue_date` | date | No | Document issue date |
|
||||
| `expiry_date` | date | No | Document expiry date |
|
||||
| `issuing_authority` | string | No | Authority that issued the document |
|
||||
| `is_verified` | boolean | Yes | Verification status |
|
||||
| `verified_at` | timestamp | No | Verification timestamp |
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### HTTP Status Codes
|
||||
|
||||
- `200` - Success
|
||||
- `201` - Created successfully
|
||||
- `400` - Bad Request
|
||||
- `401` - Unauthorized
|
||||
- `403` - Forbidden
|
||||
- `404` - Resource not found
|
||||
- `422` - Validation error
|
||||
- `500` - Internal server error
|
||||
|
||||
### Error Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"message": "Description of the error",
|
||||
"errors": {
|
||||
"field_name": ["Error message for this field"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## File Upload
|
||||
|
||||
For document uploads, use multipart/form-data:
|
||||
|
||||
```
|
||||
POST /api/practitioner-documents
|
||||
Content-Type: multipart/form-data
|
||||
|
||||
{
|
||||
"thanatopractitioner_id": 1,
|
||||
"document_type": "diplome",
|
||||
"file": [binary file],
|
||||
"issue_date": "2019-06-30",
|
||||
"expiry_date": "2024-06-30",
|
||||
"issuing_authority": "Ministère de la Santé"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Pagination
|
||||
|
||||
All list endpoints support pagination with the following query parameters:
|
||||
|
||||
- `page`: Page number (default: 1)
|
||||
- `per_page`: Items per page (default: 15, max: 100)
|
||||
|
||||
Response includes pagination metadata:
|
||||
|
||||
```json
|
||||
{
|
||||
"current_page": 1,
|
||||
"per_page": 15,
|
||||
"total": 50,
|
||||
"last_page": 4,
|
||||
"from": 1,
|
||||
"to": 15
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
API requests are rate limited to 1000 requests per hour per authenticated user. Exceeding this limit will result in a `429 Too Many Requests` response.
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
For API support or questions, please contact the development team or refer to the Laravel documentation at https://laravel.com/docs.
|
||||
411
thanasoft-back/FILE_API_DOCUMENTATION.md
Normal file
411
thanasoft-back/FILE_API_DOCUMENTATION.md
Normal file
@ -0,0 +1,411 @@
|
||||
# File Management API Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
The File Management API provides a complete CRUD system for handling file uploads with organized storage, metadata management, and file organization by categories and clients.
|
||||
|
||||
## Base URL
|
||||
|
||||
```
|
||||
/api/files
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
All file routes require authentication using Sanctum tokens.
|
||||
|
||||
## File Organization Structure
|
||||
|
||||
Files are automatically organized in storage following this structure:
|
||||
|
||||
```
|
||||
storage/app/public/
|
||||
├── client/{client_id}/{category}/{subcategory}/filename.pdf
|
||||
├── client/{client_id}/devis/DEVIS_2024_12_01_10_30_45.pdf
|
||||
├── client/{client_id}/facture/FACT_2024_12_01_10_30_45.pdf
|
||||
└── general/{category}/{subcategory}/filename.pdf
|
||||
```
|
||||
|
||||
### Supported Categories
|
||||
|
||||
- `devis` - Quotes/Devis
|
||||
- `facture` - Invoices/Factures
|
||||
- `contrat` - Contracts
|
||||
- `document` - General Documents
|
||||
- `image` - Images
|
||||
- `autre` - Other files
|
||||
|
||||
## Endpoints Overview
|
||||
|
||||
### 1. List All Files
|
||||
|
||||
```http
|
||||
GET /api/files
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `per_page` (optional): Number of files per page (default: 15)
|
||||
- `search` (optional): Search by filename
|
||||
- `mime_type` (optional): Filter by MIME type
|
||||
- `uploaded_by` (optional): Filter by uploader user ID
|
||||
- `category` (optional): Filter by category
|
||||
- `client_id` (optional): Filter by client ID
|
||||
- `date_from` (optional): Filter files uploaded after date (YYYY-MM-DD)
|
||||
- `date_to` (optional): Filter files uploaded before date (YYYY-MM-DD)
|
||||
- `sort_by` (optional): Sort field (default: uploaded_at)
|
||||
- `sort_direction` (optional): Sort direction (default: desc)
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"file_name": "document.pdf",
|
||||
"mime_type": "application/pdf",
|
||||
"size_bytes": 1024000,
|
||||
"size_formatted": "1000.00 KB",
|
||||
"extension": "pdf",
|
||||
"storage_uri": "client/123/devis/DEVIS_2024_12_01_10_30_45.pdf",
|
||||
"organized_path": "client/123/devis/DEVIS_2024_12_01_10_30_45.pdf",
|
||||
"sha256": "abc123...",
|
||||
"uploaded_by": 1,
|
||||
"uploader_name": "John Doe",
|
||||
"uploaded_at": "2024-12-01 10:30:45",
|
||||
"is_image": false,
|
||||
"is_pdf": true,
|
||||
"category": "devis",
|
||||
"subcategory": "general"
|
||||
}
|
||||
],
|
||||
"pagination": {
|
||||
"current_page": 1,
|
||||
"from": 1,
|
||||
"last_page": 5,
|
||||
"per_page": 15,
|
||||
"to": 15,
|
||||
"total": 75,
|
||||
"has_more_pages": true
|
||||
},
|
||||
"summary": {
|
||||
"total_files": 15,
|
||||
"total_size": 15360000,
|
||||
"total_size_formatted": "14.65 MB",
|
||||
"categories": {
|
||||
"devis": {
|
||||
"count": 8,
|
||||
"total_size_formatted": "8.24 MB"
|
||||
},
|
||||
"facture": {
|
||||
"count": 7,
|
||||
"total_size_formatted": "6.41 MB"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Upload File
|
||||
|
||||
```http
|
||||
POST /api/files
|
||||
```
|
||||
|
||||
**Content-Type:** `multipart/form-data`
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `file` (required): The file to upload (max 10MB)
|
||||
- `file_name` (optional): Custom filename (uses original name if not provided)
|
||||
- `category` (required): File category (devis|facture|contrat|document|image|autre)
|
||||
- `client_id` (optional): Associated client ID
|
||||
- `subcategory` (optional): Subcategory for better organization
|
||||
- `description` (optional): File description (max 500 chars)
|
||||
- `tags` (optional): Array of tags (max 10 tags, 50 chars each)
|
||||
- `is_public` (optional): Whether file is publicly accessible
|
||||
|
||||
**Example Request:**
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
http://localhost/api/files \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-F "file=@/path/to/document.pdf" \
|
||||
-F "category=devis" \
|
||||
-F "client_id=123" \
|
||||
-F "subcategory=annual" \
|
||||
-F "description=Annual quote for client" \
|
||||
-F 'tags[]=quote' \
|
||||
-F 'tags[]=annual'
|
||||
```
|
||||
|
||||
**Success Response (201):**
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"id": 1,
|
||||
"file_name": "document.pdf",
|
||||
"mime_type": "application/pdf",
|
||||
"size_bytes": 1024000,
|
||||
"storage_uri": "client/123/devis/annual/document_2024_12_01_10_30_45.pdf",
|
||||
"category": "devis",
|
||||
"subcategory": "annual",
|
||||
"uploaded_at": "2024-12-01 10:30:45"
|
||||
},
|
||||
"message": "Fichier téléchargé avec succès."
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Get File Details
|
||||
|
||||
```http
|
||||
GET /api/files/{id}
|
||||
```
|
||||
|
||||
**Response:** Same as file object in list endpoint
|
||||
|
||||
### 4. Update File Metadata
|
||||
|
||||
```http
|
||||
PUT /api/files/{id}
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `file_name` (optional): New filename
|
||||
- `description` (optional): Updated description
|
||||
- `tags` (optional): Updated tags array
|
||||
- `is_public` (optional): Updated public status
|
||||
- `category` (optional): Move to new category
|
||||
- `client_id` (optional): Change associated client
|
||||
- `subcategory` (optional): Update subcategory
|
||||
|
||||
**Note:** If category, client_id, or subcategory are changed, the file will be automatically moved to the new location.
|
||||
|
||||
### 5. Delete File
|
||||
|
||||
```http
|
||||
DELETE /api/files/{id}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Fichier supprimé avec succès."
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Get Files by Category
|
||||
|
||||
```http
|
||||
GET /api/files/by-category/{category}
|
||||
```
|
||||
|
||||
**Parameters:** Same as list endpoint with additional `per_page`
|
||||
|
||||
### 7. Get Files by Client
|
||||
|
||||
```http
|
||||
GET /api/files/by-client/{clientId}
|
||||
```
|
||||
|
||||
**Parameters:** Same as list endpoint with additional `per_page`
|
||||
|
||||
### 8. Get Organized File Structure
|
||||
|
||||
```http
|
||||
GET /api/files/organized
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"devis/general": {
|
||||
"category": "devis",
|
||||
"subcategory": "general",
|
||||
"files": [...],
|
||||
"count": 25
|
||||
},
|
||||
"facture/annual": {
|
||||
"category": "facture",
|
||||
"subcategory": "annual",
|
||||
"files": [...],
|
||||
"count": 15
|
||||
}
|
||||
},
|
||||
"message": "Structure de fichiers récupérée avec succès."
|
||||
}
|
||||
```
|
||||
|
||||
### 9. Get Storage Statistics
|
||||
|
||||
```http
|
||||
GET /api/files/statistics
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"total_files": 150,
|
||||
"total_size_bytes": 1073741824,
|
||||
"total_size_formatted": "1.00 GB",
|
||||
"by_type": {
|
||||
"application/pdf": {
|
||||
"count": 85,
|
||||
"total_size": 734003200
|
||||
},
|
||||
"image/jpeg": {
|
||||
"count": 45,
|
||||
"total_size": 209715200
|
||||
}
|
||||
},
|
||||
"by_category": {
|
||||
"devis": {
|
||||
"count": 60,
|
||||
"total_size": 429496729
|
||||
},
|
||||
"facture": {
|
||||
"count": 50,
|
||||
"total_size": 322122547
|
||||
}
|
||||
}
|
||||
},
|
||||
"message": "Statistiques de stockage récupérées avec succès."
|
||||
}
|
||||
```
|
||||
|
||||
### 10. Generate Download URL
|
||||
|
||||
```http
|
||||
GET /api/files/{id}/download
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"download_url": "/storage/client/123/devis/document_2024_12_01_10_30_45.pdf",
|
||||
"file_name": "document.pdf",
|
||||
"mime_type": "application/pdf"
|
||||
},
|
||||
"message": "URL de téléchargement générée avec succès."
|
||||
}
|
||||
```
|
||||
|
||||
## Error Responses
|
||||
|
||||
### Validation Error (422)
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Les données fournies ne sont pas valides.",
|
||||
"errors": {
|
||||
"file": ["Le fichier est obligatoire."],
|
||||
"category": ["La catégorie est obligatoire."]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Not Found (404)
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Fichier non trouvé."
|
||||
}
|
||||
```
|
||||
|
||||
### Server Error (500)
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Une erreur est survenue lors du traitement de la requête.",
|
||||
"error": "Detailed error message (only in debug mode)"
|
||||
}
|
||||
```
|
||||
|
||||
## File Features
|
||||
|
||||
### Automatic Organization
|
||||
|
||||
- Files are automatically organized by category and client
|
||||
- Timestamps are added to prevent filename conflicts
|
||||
- Safe slug generation for subcategories
|
||||
|
||||
### Security
|
||||
|
||||
- SHA256 hash calculation for file integrity
|
||||
- User-based access control
|
||||
- File size validation (10MB limit)
|
||||
|
||||
### Metadata Support
|
||||
|
||||
- MIME type detection
|
||||
- File size tracking
|
||||
- Upload timestamp
|
||||
- User attribution
|
||||
- Custom tags and descriptions
|
||||
|
||||
### Storage Management
|
||||
|
||||
- Public storage disk usage
|
||||
- Efficient path organization
|
||||
- Duplicate prevention through hashing
|
||||
- Automatic file moving on metadata updates
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Upload a Quote for Client
|
||||
|
||||
```bash
|
||||
curl -X POST \
|
||||
http://localhost/api/files \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-F "file=@quote_2024.pdf" \
|
||||
-F "category=devis" \
|
||||
-F "client_id=123" \
|
||||
-F "subcategory=annual_2024" \
|
||||
-F 'tags[]=quote' \
|
||||
-F 'tags[]=annual'
|
||||
```
|
||||
|
||||
### Get All Client Files
|
||||
|
||||
```bash
|
||||
curl -X GET \
|
||||
"http://localhost/api/files/by-client/123?per_page=20&sort_by=uploaded_at&sort_direction=desc" \
|
||||
-H "Authorization: Bearer {token}"
|
||||
```
|
||||
|
||||
### Get File Statistics
|
||||
|
||||
```bash
|
||||
curl -X GET \
|
||||
"http://localhost/api/files/statistics" \
|
||||
-H "Authorization: Bearer {token}"
|
||||
```
|
||||
|
||||
### Search Files
|
||||
|
||||
```bash
|
||||
curl -X GET \
|
||||
"http://localhost/api/files?search=annual&category=devis" \
|
||||
-H "Authorization: Bearer {token}"
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- All file operations are logged for audit purposes
|
||||
- Files are stored in `storage/app/public/` directory
|
||||
- The system automatically handles file moving when categories change
|
||||
- Download URLs are generated on-demand for security
|
||||
- Pagination is applied to all list endpoints
|
||||
- French language is used for all API messages and validations
|
||||
219
thanasoft-back/IMPLEMENTATION_SUMMARY.md
Normal file
219
thanasoft-back/IMPLEMENTATION_SUMMARY.md
Normal file
@ -0,0 +1,219 @@
|
||||
# Intervention with All Data - Implementation Summary
|
||||
|
||||
## Overview
|
||||
|
||||
Created a comprehensive `createInterventionalldata` method in the InterventionController that handles creating an intervention along with all related entities (deceased, client, contact, location, documents) in a single atomic transaction.
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
### 1. Form Request
|
||||
|
||||
**File**: `app/Http/Requests/StoreInterventionWithAllDataRequest.php`
|
||||
|
||||
- Comprehensive validation for all entities
|
||||
- Step-level error grouping (deceased, client, contact, location, documents, intervention)
|
||||
- French error messages
|
||||
- File validation for document uploads
|
||||
|
||||
### 2. Controller Method
|
||||
|
||||
**File**: `app/Http/Controllers/Api/InterventionController.php`
|
||||
|
||||
- Added `createInterventionalldata()` method
|
||||
- Database transaction wrapping all operations
|
||||
- Step-by-step creation flow:
|
||||
1. Create deceased
|
||||
2. Create client
|
||||
3. Create contact (if provided)
|
||||
4. Prepare location data
|
||||
5. Create intervention
|
||||
6. Handle document uploads
|
||||
- Automatic rollback on any error
|
||||
- Comprehensive logging
|
||||
|
||||
### 3. API Route
|
||||
|
||||
**File**: `routes/api.php`
|
||||
|
||||
- Added endpoint: `POST /api/interventions/with-all-data`
|
||||
- Protected by authentication middleware
|
||||
|
||||
### 4. Repository Improvements
|
||||
|
||||
**Files**:
|
||||
|
||||
- `app/Repositories/DeceasedRepositoryInterface.php` (created)
|
||||
- `app/Repositories/DeceasedRepository.php` (updated)
|
||||
|
||||
**Changes**:
|
||||
|
||||
- DeceasedRepository now extends BaseRepository
|
||||
- Implements BaseRepositoryInterface
|
||||
- Inherits transaction support from BaseRepository
|
||||
- Consistent with other repository implementations
|
||||
|
||||
## API Endpoint
|
||||
|
||||
### POST /api/interventions/with-all-data
|
||||
|
||||
#### Request Structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"deceased": {
|
||||
"last_name": "Required",
|
||||
"first_name": "Optional",
|
||||
"birth_date": "Optional",
|
||||
"death_date": "Optional",
|
||||
"place_of_death": "Optional",
|
||||
"notes": "Optional"
|
||||
},
|
||||
"client": {
|
||||
"name": "Required",
|
||||
"vat_number": "Optional",
|
||||
"siret": "Optional",
|
||||
"email": "Optional",
|
||||
"phone": "Optional",
|
||||
"billing_address_line1": "Optional",
|
||||
"billing_postal_code": "Optional",
|
||||
"billing_city": "Optional",
|
||||
"billing_country_code": "Optional",
|
||||
"notes": "Optional",
|
||||
"is_active": "Optional"
|
||||
},
|
||||
"contact": {
|
||||
"first_name": "Optional",
|
||||
"last_name": "Optional",
|
||||
"email": "Optional",
|
||||
"phone": "Optional",
|
||||
"role": "Optional"
|
||||
},
|
||||
"location": {
|
||||
"name": "Optional",
|
||||
"address": "Optional",
|
||||
"city": "Optional",
|
||||
"postal_code": "Optional",
|
||||
"country_code": "Optional",
|
||||
"access_instructions": "Optional",
|
||||
"notes": "Optional"
|
||||
},
|
||||
"documents": [
|
||||
{
|
||||
"file": "File upload",
|
||||
"name": "Required",
|
||||
"description": "Optional"
|
||||
}
|
||||
],
|
||||
"intervention": {
|
||||
"type": "Required",
|
||||
"scheduled_at": "Optional",
|
||||
"duration_min": "Optional",
|
||||
"status": "Optional",
|
||||
"assigned_practitioner_id": "Optional",
|
||||
"order_giver": "Optional",
|
||||
"notes": "Optional",
|
||||
"created_by": "Optional"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Success Response (201):
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Intervention créée avec succès",
|
||||
"data": {
|
||||
"intervention": { ... },
|
||||
"deceased": { ... },
|
||||
"client": { ... },
|
||||
"contact_id": 123,
|
||||
"documents_count": 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Error Response (422 - Validation):
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Données invalides",
|
||||
"errors": {
|
||||
"deceased": ["Le nom de famille est obligatoire."],
|
||||
"client": ["Le nom du client est obligatoire."],
|
||||
"intervention": ["Le type d'intervention est obligatoire."]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Transaction Flow
|
||||
|
||||
```
|
||||
DB::beginTransaction()
|
||||
1. Create Deceased
|
||||
2. Create Client
|
||||
3. Create Contact (if provided)
|
||||
4. Prepare Location Notes
|
||||
5. Create Intervention
|
||||
6. Handle Document Uploads (pending implementation)
|
||||
DB::commit()
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Validation Errors
|
||||
|
||||
- Caught separately with proper HTTP status (422)
|
||||
- Grouped by step for better UX
|
||||
- French error messages
|
||||
|
||||
### General Errors
|
||||
|
||||
- Caught with proper HTTP status (500)
|
||||
- Full exception logged with trace
|
||||
- Input data logged (excluding files)
|
||||
- Transaction automatically rolled back
|
||||
|
||||
## Data Integrity
|
||||
|
||||
1. **BaseRepository Transaction Support**: All repository create operations use transactions
|
||||
2. **Controller-level Transaction**: Main transaction wraps all operations
|
||||
3. **Automatic Rollback**: Any exception triggers automatic rollback
|
||||
4. **Validation Before Transaction**: All data validated before any DB operations
|
||||
|
||||
## Benefits
|
||||
|
||||
1. **Atomicity**: All-or-nothing operation - prevents partial data creation
|
||||
2. **Data Integrity**: No orphaned records if any step fails
|
||||
3. **Performance**: Single HTTP request for complex operation
|
||||
4. **User Experience**: One form instead of multiple steps
|
||||
5. **Validation**: Comprehensive client and server-side validation
|
||||
6. **Logging**: Full audit trail for debugging
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Document Upload Implementation**: Complete file upload and storage logic
|
||||
2. **Location Model**: Consider creating a proper Location model instead of notes
|
||||
3. **Client Location Association**: Link interventions to actual locations
|
||||
4. **File Storage**: Implement proper file storage with intervention folders
|
||||
5. **Email Notifications**: Add notifications to relevant parties
|
||||
6. **Audit Trail**: Add more detailed logging for compliance
|
||||
|
||||
## Testing
|
||||
|
||||
To test the endpoint:
|
||||
|
||||
```bash
|
||||
# Create intervention with all data
|
||||
curl -X POST http://localhost/api/interventions/with-all-data \
|
||||
-H "Authorization: Bearer {token}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @intervention-data.json
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- All date fields should use ISO format (Y-m-d H:i:s)
|
||||
- Document files should be sent as multipart/form-data
|
||||
- Location data is currently appended to intervention notes (can be enhanced)
|
||||
- Contact creation is optional - only created if data provided
|
||||
- Default status is "demande" if not specified
|
||||
@ -0,0 +1,252 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Repositories\AccessControlRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class AccessControlController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly AccessControlRepositoryInterface $accessControlRepository
|
||||
) {
|
||||
}
|
||||
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
try {
|
||||
return response()->json([
|
||||
'data' => $this->accessControlRepository->index(),
|
||||
'message' => 'Roles et permissions recuperes avec succes.',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching access control data: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la recuperation des roles et permissions.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function storeRole(Request $request): JsonResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => ['required', 'string', 'max:100', 'unique:roles,name'],
|
||||
'guard_name' => ['nullable', 'string', 'max:50'],
|
||||
'permissions' => ['nullable', 'array'],
|
||||
'permissions.*' => ['string', 'max:150'],
|
||||
]);
|
||||
|
||||
try {
|
||||
$role = $this->accessControlRepository->createRole($validated);
|
||||
|
||||
return response()->json([
|
||||
'data' => $role,
|
||||
'message' => 'Role cree avec succes.',
|
||||
], 201);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating role: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'data' => $validated,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la creation du role.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function updateRole(Request $request, string $id): JsonResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => ['sometimes', 'string', 'max:100', 'unique:roles,name,' . $id],
|
||||
'guard_name' => ['nullable', 'string', 'max:50'],
|
||||
'permissions' => ['nullable', 'array'],
|
||||
'permissions.*' => ['string', 'max:150'],
|
||||
]);
|
||||
|
||||
try {
|
||||
$role = $this->accessControlRepository->updateRole((int) $id, $validated);
|
||||
|
||||
if (! $role) {
|
||||
return response()->json([
|
||||
'message' => 'Role non trouve.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'data' => $role,
|
||||
'message' => 'Role mis a jour avec succes.',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating role: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'role_id' => $id,
|
||||
'data' => $validated,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise a jour du role.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function destroyRole(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->accessControlRepository->deleteRole((int) $id);
|
||||
|
||||
if (! $deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Role non trouve.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Role supprime avec succes.',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting role: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'role_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression du role.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function syncRolePermissions(Request $request, string $id): JsonResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'permissions' => ['required', 'array'],
|
||||
'permissions.*' => ['string', 'max:150'],
|
||||
]);
|
||||
|
||||
try {
|
||||
$role = $this->accessControlRepository->syncRolePermissions((int) $id, $validated['permissions']);
|
||||
|
||||
if (! $role) {
|
||||
return response()->json([
|
||||
'message' => 'Role non trouve.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'data' => $role,
|
||||
'message' => 'Permissions du role synchronisees avec succes.',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error syncing role permissions: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'role_id' => $id,
|
||||
'data' => $validated,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la synchronisation des permissions du role.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function storePermission(Request $request): JsonResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => ['required', 'string', 'max:150', 'unique:permissions,name'],
|
||||
'guard_name' => ['nullable', 'string', 'max:50'],
|
||||
]);
|
||||
|
||||
try {
|
||||
$permission = $this->accessControlRepository->createPermission($validated);
|
||||
|
||||
return response()->json([
|
||||
'data' => $permission,
|
||||
'message' => 'Permission creee avec succes.',
|
||||
], 201);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating permission: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'data' => $validated,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la creation de la permission.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function updatePermission(Request $request, string $id): JsonResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => ['sometimes', 'string', 'max:150', 'unique:permissions,name,' . $id],
|
||||
'guard_name' => ['nullable', 'string', 'max:50'],
|
||||
]);
|
||||
|
||||
try {
|
||||
$permission = $this->accessControlRepository->updatePermission((int) $id, $validated);
|
||||
|
||||
if (! $permission) {
|
||||
return response()->json([
|
||||
'message' => 'Permission non trouvee.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'data' => $permission,
|
||||
'message' => 'Permission mise a jour avec succes.',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating permission: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'permission_id' => $id,
|
||||
'data' => $validated,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise a jour de la permission.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function destroyPermission(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->accessControlRepository->deletePermission((int) $id);
|
||||
|
||||
if (! $deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Permission non trouvee.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Permission supprimee avec succes.',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting permission: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'permission_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression de la permission.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -19,6 +19,10 @@ class AuthController extends BaseController
|
||||
$data = $request->validate([
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email'],
|
||||
'roles' => ['nullable', 'array'],
|
||||
'roles.*' => ['string', 'max:100'],
|
||||
'permissions' => ['nullable', 'array'],
|
||||
'permissions.*' => ['string', 'max:150'],
|
||||
'password' => ['required', Password::min(8)],
|
||||
|
||||
]);
|
||||
@ -29,10 +33,18 @@ class AuthController extends BaseController
|
||||
'password' => $data['password'], // hashed via User model cast
|
||||
]);
|
||||
|
||||
if (! empty($data['roles'])) {
|
||||
$user->syncRoles($data['roles']);
|
||||
}
|
||||
|
||||
if (! empty($data['permissions'])) {
|
||||
$user->syncPermissions($data['permissions']);
|
||||
}
|
||||
|
||||
$token = $user->createToken('api')->plainTextToken;
|
||||
|
||||
return $this->sendResponse([
|
||||
'user' => $user,
|
||||
'user' => $user->load('roles', 'permissions'),
|
||||
'token' => $token,
|
||||
], 'User registered successfully.');
|
||||
|
||||
@ -61,7 +73,7 @@ class AuthController extends BaseController
|
||||
$token = $user->createToken('api')->plainTextToken;
|
||||
|
||||
return $this->sendResponse([
|
||||
'user' => $user,
|
||||
'user' => $user->load('roles', 'permissions'),
|
||||
'token' => $token,
|
||||
], 'Login successful.');
|
||||
|
||||
@ -72,6 +84,75 @@ class AuthController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
public function checkEmail(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$data = $request->validate([
|
||||
'email' => ['required', 'email'],
|
||||
]);
|
||||
|
||||
$user = User::where('email', $data['email'])->first();
|
||||
|
||||
if (! $user) {
|
||||
return $this->sendError('Utilisateur introuvable.', [
|
||||
'email' => ['Aucun utilisateur ne correspond a cet email.'],
|
||||
], 404);
|
||||
}
|
||||
|
||||
return $this->sendResponse([
|
||||
'user' => [
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'email' => $user->email,
|
||||
],
|
||||
'has_password' => ! empty($user->getRawOriginal('password')),
|
||||
], 'Email verifie avec succes.');
|
||||
} catch (ValidationException $e) {
|
||||
return $this->sendError('Validation Error.', $e->errors(), 422);
|
||||
} catch (\Exception $e) {
|
||||
return $this->sendError('Email check failed.', ['error' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function createPasswordAndLogin(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$data = $request->validate([
|
||||
'email' => ['required', 'email'],
|
||||
'password' => ['required', 'confirmed', Password::min(8)],
|
||||
]);
|
||||
|
||||
/** @var User|null $user */
|
||||
$user = User::where('email', $data['email'])->first();
|
||||
|
||||
if (! $user) {
|
||||
return $this->sendError('Utilisateur introuvable.', [
|
||||
'email' => ['Aucun utilisateur ne correspond a cet email.'],
|
||||
], 404);
|
||||
}
|
||||
|
||||
if (! empty($user->getRawOriginal('password'))) {
|
||||
return $this->sendError('Mot de passe deja defini.', [
|
||||
'password' => ['Cet utilisateur a deja un mot de passe.'],
|
||||
], 422);
|
||||
}
|
||||
|
||||
$user->password = $data['password'];
|
||||
$user->save();
|
||||
|
||||
$token = $user->createToken('api')->plainTextToken;
|
||||
|
||||
return $this->sendResponse([
|
||||
'user' => $user->load('roles', 'permissions'),
|
||||
'token' => $token,
|
||||
], 'Mot de passe cree et connexion reussie.');
|
||||
} catch (ValidationException $e) {
|
||||
return $this->sendError('Validation Error.', $e->errors(), 422);
|
||||
} catch (\Exception $e) {
|
||||
return $this->sendError('Password creation failed.', ['error' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function me(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
@ -81,7 +162,7 @@ class AuthController extends BaseController
|
||||
return $this->sendError('Unauthenticated.', [], 401);
|
||||
}
|
||||
|
||||
return $this->sendResponse($user, 'User retrieved successfully.');
|
||||
return $this->sendResponse($user->load('roles', 'permissions'), 'User retrieved successfully.');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return $this->sendError('Failed to retrieve user.', ['error' => $e->getMessage()], 500);
|
||||
|
||||
151
thanasoft-back/app/Http/Controllers/Api/AvoirController.php
Normal file
151
thanasoft-back/app/Http/Controllers/Api/AvoirController.php
Normal file
@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreAvoirRequest;
|
||||
use App\Http\Requests\UpdateAvoirRequest;
|
||||
use App\Http\Resources\AvoirResource;
|
||||
use App\Repositories\AvoirRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class AvoirController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected AvoirRepositoryInterface $avoirRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of credits.
|
||||
*/
|
||||
public function index(): AnonymousResourceCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$avoirs = $this->avoirRepository->all();
|
||||
return AvoirResource::collection($avoirs);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching avoirs: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des avoirs.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created credit.
|
||||
*/
|
||||
public function store(StoreAvoirRequest $request): AvoirResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$avoir = $this->avoirRepository->create($request->validated());
|
||||
return new AvoirResource($avoir);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating avoir: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création de l\'avoir.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified credit.
|
||||
*/
|
||||
public function show(string $id): AvoirResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$avoir = $this->avoirRepository->find($id);
|
||||
|
||||
if (!$avoir) {
|
||||
return response()->json([
|
||||
'message' => 'Avoir non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new AvoirResource($avoir);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching avoir: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'avoir_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération de l\'avoir.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified credit.
|
||||
*/
|
||||
public function update(UpdateAvoirRequest $request, string $id): AvoirResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->avoirRepository->update($id, $request->validated());
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Avoir non trouvé ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$avoir = $this->avoirRepository->find($id);
|
||||
return new AvoirResource($avoir);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating avoir: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'avoir_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour de l\'avoir.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified credit.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->avoirRepository->delete($id);
|
||||
|
||||
if (!$deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Avoir non trouvé ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Avoir supprimé avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting avoir: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'avoir_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression de l\'avoir.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Client;
|
||||
use App\Repositories\ClientActivityTimelineRepositoryInterface;
|
||||
use App\Http\Resources\Client\ClientActivityTimelineResource;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ClientActivityTimelineController extends Controller
|
||||
{
|
||||
protected $repository;
|
||||
|
||||
public function __construct(ClientActivityTimelineRepositoryInterface $repository)
|
||||
{
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get activity timeline for a client
|
||||
*/
|
||||
public function index(Request $request, Client $client)
|
||||
{
|
||||
try {
|
||||
$perPage = (int) $request->get('per_page', 10);
|
||||
|
||||
$activities = $this->repository->getByClient($client->id, $perPage);
|
||||
|
||||
return ClientActivityTimelineResource::collection($activities);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Error fetching client timeline: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ClientCategoryRequest;
|
||||
use App\Http\Resources\Client\ClientCategoryResource;
|
||||
use App\Http\Resources\ClientResource;
|
||||
use App\Models\ClientCategory;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
|
||||
class ClientCategoryController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(): AnonymousResourceCollection
|
||||
{
|
||||
$query = ClientCategory::query();
|
||||
$categories = $query->get();
|
||||
|
||||
return ClientCategoryResource::collection($categories);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(ClientCategoryRequest $request): ClientCategoryResource
|
||||
{
|
||||
$category = ClientCategory::create($request->validated());
|
||||
|
||||
return new ClientCategoryResource($category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(ClientCategory $clientCategory): ClientCategoryResource
|
||||
{
|
||||
return new ClientCategoryResource($clientCategory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource by slug.
|
||||
*/
|
||||
public function showBySlug(string $slug): ClientCategoryResource
|
||||
{
|
||||
$category = ClientCategory::where('slug', $slug)->firstOrFail();
|
||||
|
||||
return new ClientCategoryResource($category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(ClientCategoryRequest $request, ClientCategory $clientCategory): ClientCategoryResource
|
||||
{
|
||||
$clientCategory->update($request->validated());
|
||||
|
||||
return new ClientCategoryResource($clientCategory->fresh());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(ClientCategory $clientCategory): JsonResponse
|
||||
{
|
||||
// Check if category has clients
|
||||
if ($clientCategory->clients()->exists()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'Cannot delete category that has clients assigned. Please reassign clients first.'
|
||||
], 422);
|
||||
}
|
||||
|
||||
$clientCategory->delete();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Client category deleted successfully.'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle active status of the category.
|
||||
*/
|
||||
public function toggleStatus(ClientCategory $clientCategory, Request $request): ClientCategoryResource
|
||||
{
|
||||
$request->validate([
|
||||
'is_active' => 'required|boolean',
|
||||
]);
|
||||
|
||||
$clientCategory->update([
|
||||
'is_active' => $request->boolean('is_active'),
|
||||
]);
|
||||
|
||||
return new ClientCategoryResource($clientCategory->fresh());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get clients for a specific category.
|
||||
*/
|
||||
public function clients(ClientCategory $clientCategory, Request $request): AnonymousResourceCollection
|
||||
{
|
||||
$query = $clientCategory->clients();
|
||||
|
||||
// Active status filter
|
||||
if ($request->has('is_active')) {
|
||||
$query->where('is_active', $request->boolean('is_active'));
|
||||
}
|
||||
|
||||
// Pagination
|
||||
$perPage = $request->get('per_page', 15);
|
||||
$clients = $query->paginate($perPage);
|
||||
|
||||
return ClientResource::collection($clients);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorder categories.
|
||||
*/
|
||||
public function reorder(Request $request): JsonResponse
|
||||
{
|
||||
$request->validate([
|
||||
'order' => 'required|array',
|
||||
'order.*' => 'integer|exists:client_categories,id',
|
||||
]);
|
||||
|
||||
foreach ($request->order as $index => $categoryId) {
|
||||
ClientCategory::where('id', $categoryId)->update(['sort_order' => $index]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'Categories reordered successfully.'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active categories for dropdowns.
|
||||
*/
|
||||
public function active(): AnonymousResourceCollection
|
||||
{
|
||||
$categories = ClientCategory::where('is_active', true)
|
||||
->orderBy('sort_order', 'asc')
|
||||
->orderBy('name', 'asc')
|
||||
->get();
|
||||
|
||||
return ClientCategoryResource::collection($categories);
|
||||
}
|
||||
}
|
||||
339
thanasoft-back/app/Http/Controllers/Api/ClientController.php
Normal file
339
thanasoft-back/app/Http/Controllers/Api/ClientController.php
Normal file
@ -0,0 +1,339 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreClientRequest;
|
||||
use App\Http\Requests\UpdateClientRequest;
|
||||
use App\Http\Resources\Client\ClientResource;
|
||||
use App\Http\Resources\Client\ClientCollection;
|
||||
use App\Repositories\ClientRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class ClientController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ClientRepositoryInterface $clientRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of clients.
|
||||
*/
|
||||
public function index(Request $request): ClientCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = (int) $request->get('per_page', 15);
|
||||
$filters = [
|
||||
'search' => $request->get('search'),
|
||||
'is_active' => $request->get('is_active'),
|
||||
'group_id' => $request->get('group_id'),
|
||||
'client_category_id' => $request->get('client_category_id'),
|
||||
'sort_by' => $request->get('sort_by', 'created_at'),
|
||||
'sort_direction' => $request->get('sort_direction', 'desc'),
|
||||
];
|
||||
|
||||
// Remove null filters
|
||||
$filters = array_filter($filters, function ($value) {
|
||||
return $value !== null && $value !== '';
|
||||
});
|
||||
|
||||
$clients = $this->clientRepository->paginate($perPage, $filters);
|
||||
|
||||
return new ClientCollection($clients);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching clients: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des clients.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created client.
|
||||
*/
|
||||
public function store(StoreClientRequest $request): ClientResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$client = $this->clientRepository->create($request->validated());
|
||||
return new ClientResource($client);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating client: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création du client.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified client.
|
||||
*/
|
||||
public function show(string $id): ClientResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$client = $this->clientRepository->find($id);
|
||||
|
||||
if (!$client) {
|
||||
return response()->json([
|
||||
'message' => 'Client non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new ClientResource($client);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching client: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'client_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du client.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function searchBy(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$name = $request->get('name', '');
|
||||
|
||||
if (empty($name)) {
|
||||
return response()->json([
|
||||
'message' => 'Le paramètre "name" est requis.',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$clients = $this->clientRepository->searchByName($name);
|
||||
|
||||
return response()->json([
|
||||
'data' => $clients,
|
||||
'count' => $clients->count(),
|
||||
'message' => $clients->count() > 0
|
||||
? 'Clients trouvés avec succès.'
|
||||
: 'Aucun client trouvé.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error searching clients by name: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'search_term' => $name,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la recherche des clients.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified client.
|
||||
*/
|
||||
public function update(UpdateClientRequest $request, string $id): ClientResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$validatedData = $request->validated();
|
||||
|
||||
$client = $this->clientRepository->find($id);
|
||||
if (!$client) {
|
||||
return response()->json([
|
||||
'message' => 'Client non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
if ($request->hasFile('avatar')) {
|
||||
// Delete old avatar if exists
|
||||
if ($client->avatar) {
|
||||
Storage::disk('public')->delete($client->avatar);
|
||||
}
|
||||
|
||||
// Store new avatar
|
||||
$path = $request->file('avatar')->store('avatars', 'public');
|
||||
$validatedData['avatar'] = $path;
|
||||
}
|
||||
|
||||
$updated = $this->clientRepository->update($id, $validatedData);
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Échec de la mise à jour.',
|
||||
], 500);
|
||||
}
|
||||
|
||||
$client = $this->clientRepository->find($id);
|
||||
return new ClientResource($client);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating client: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'client_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du client.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified client.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->clientRepository->delete($id);
|
||||
|
||||
if (!$deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Client non trouvé ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Client supprimé avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting client: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'client_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression du client.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Change client status (active/inactive).
|
||||
*/
|
||||
public function changeStatus(Request $request, string $id): ClientResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$isActive = $request->input('is_active');
|
||||
$updated = $this->clientRepository->update($id, ['is_active' => $isActive]);
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Client non trouvé ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$client = $this->clientRepository->find($id);
|
||||
return new ClientResource($client);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error changing client status: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'client_id' => $id,
|
||||
]);
|
||||
return response()->json(['message' => 'Erreur serveur'], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get children clients.
|
||||
*/
|
||||
public function getChildren(string $id): AnonymousResourceCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$client = $this->clientRepository->find($id);
|
||||
if (!$client) {
|
||||
return response()->json(['message' => 'Client not found'], 404);
|
||||
}
|
||||
|
||||
// Assuming the relationship is defined in the model as 'children'
|
||||
$children = $client->children;
|
||||
return ClientResource::collection($children);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching children: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'client_id' => $id,
|
||||
]);
|
||||
return response()->json(['message' => 'Erreur serveur'], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a child client.
|
||||
*/
|
||||
public function addChild(string $id, string $childId): JsonResponse
|
||||
{
|
||||
try {
|
||||
$parent = $this->clientRepository->find($id);
|
||||
$child = $this->clientRepository->find($childId);
|
||||
|
||||
if (!$parent || !$child) {
|
||||
return response()->json(['message' => 'Parent or Child not found'], 404);
|
||||
}
|
||||
|
||||
// Update child's parent_id
|
||||
$this->clientRepository->update($childId, ['parent_id' => $id]);
|
||||
|
||||
return response()->json(['message' => 'Child added successfully'], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error adding child: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'parent_id' => $id,
|
||||
'child_id' => $childId
|
||||
]);
|
||||
return response()->json(['message' => 'Erreur serveur'], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a child client.
|
||||
*/
|
||||
public function removeChild(string $id, string $childId): JsonResponse
|
||||
{
|
||||
try {
|
||||
$child = $this->clientRepository->find($childId);
|
||||
|
||||
if (!$child) {
|
||||
return response()->json(['message' => 'Child not found'], 404);
|
||||
}
|
||||
|
||||
if ($child->parent_id != $id) {
|
||||
return response()->json(['message' => 'Client is not a child of this parent'], 400);
|
||||
}
|
||||
|
||||
// Remove parent_id
|
||||
$this->clientRepository->update($childId, ['parent_id' => null]);
|
||||
|
||||
return response()->json(['message' => 'Child removed successfully'], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error removing child: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'parent_id' => $id,
|
||||
'child_id' => $childId
|
||||
]);
|
||||
return response()->json(['message' => 'Erreur serveur'], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,257 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\AssignClientsToGroupRequest;
|
||||
use App\Http\Requests\StoreClientGroupRequest;
|
||||
use App\Http\Requests\UpdateClientGroupRequest;
|
||||
use App\Http\Resources\Client\ClientGroupResource;
|
||||
use App\Models\Client;
|
||||
use App\Repositories\ClientGroupRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ClientGroupController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ClientGroupRepositoryInterface $clientGroupRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of client groups.
|
||||
*/
|
||||
public function index(Request $request): AnonymousResourceCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = (int) $request->get('per_page', 5);
|
||||
$filters = array_filter([
|
||||
'search' => $request->get('search'),
|
||||
'sort_by' => $request->get('sort_by', 'created_at'),
|
||||
'sort_direction' => $request->get('sort_direction', 'desc'),
|
||||
], static fn ($value) => $value !== null && $value !== '');
|
||||
|
||||
$clientGroups = $this->clientGroupRepository->paginate($perPage, $filters);
|
||||
|
||||
return ClientGroupResource::collection($clientGroups);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching client groups: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des groupes de clients.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created client group.
|
||||
*/
|
||||
public function store(StoreClientGroupRequest $request): ClientGroupResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$validated = $request->validated();
|
||||
|
||||
$clientGroup = DB::transaction(function () use ($validated) {
|
||||
$clientIds = Arr::get($validated, 'client_ids', []);
|
||||
|
||||
$clientGroup = $this->clientGroupRepository->create(Arr::except($validated, ['client_ids']));
|
||||
|
||||
if (!empty($clientIds)) {
|
||||
Client::query()
|
||||
->whereIn('id', $clientIds)
|
||||
->update(['group_id' => $clientGroup->id]);
|
||||
}
|
||||
|
||||
return $clientGroup->load(['clients'])->loadCount('clients');
|
||||
});
|
||||
|
||||
return new ClientGroupResource($clientGroup);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating client group: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création du groupe de clients.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified client group.
|
||||
*/
|
||||
public function show(string $id): ClientGroupResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$clientGroup = $this->clientGroupRepository->find($id);
|
||||
|
||||
if (!$clientGroup) {
|
||||
return response()->json([
|
||||
'message' => 'Groupe de clients non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$clientGroup->load(['clients'])->loadCount('clients');
|
||||
|
||||
return new ClientGroupResource($clientGroup);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching client group: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'client_group_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du groupe de clients.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified client group.
|
||||
*/
|
||||
public function update(UpdateClientGroupRequest $request, string $id): ClientGroupResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$validated = $request->validated();
|
||||
|
||||
$updated = DB::transaction(function () use ($id, $validated) {
|
||||
$updated = $this->clientGroupRepository->update($id, Arr::except($validated, ['client_ids']));
|
||||
|
||||
if (!$updated) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (array_key_exists('client_ids', $validated)) {
|
||||
$clientIds = $validated['client_ids'] ?? [];
|
||||
|
||||
Client::query()
|
||||
->where('group_id', (int) $id)
|
||||
->whereNotIn('id', $clientIds)
|
||||
->update(['group_id' => null]);
|
||||
|
||||
if (!empty($clientIds)) {
|
||||
Client::query()
|
||||
->whereIn('id', $clientIds)
|
||||
->update(['group_id' => (int) $id]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Groupe de clients non trouvé ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$clientGroup = $this->clientGroupRepository->find($id);
|
||||
$clientGroup?->load(['clients'])->loadCount('clients');
|
||||
|
||||
return new ClientGroupResource($clientGroup);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating client group: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'client_group_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du groupe de clients.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified client group.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->clientGroupRepository->delete($id);
|
||||
|
||||
if (!$deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Groupe de clients non trouvé ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Groupe de clients supprimé avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting client group: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'client_group_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression du groupe de clients.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign many clients to one client group.
|
||||
*/
|
||||
public function assignClients(AssignClientsToGroupRequest $request, string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$clientGroup = $this->clientGroupRepository->find($id);
|
||||
|
||||
if (!$clientGroup) {
|
||||
return response()->json([
|
||||
'message' => 'Groupe de clients non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$clientIds = $request->validated('client_ids');
|
||||
|
||||
$updatedCount = DB::transaction(function () use ($clientIds, $clientGroup) {
|
||||
return Client::query()
|
||||
->whereIn('id', $clientIds)
|
||||
->update(['group_id' => $clientGroup->id]);
|
||||
});
|
||||
|
||||
$clientGroup->load(['clients'])->loadCount('clients');
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Clients assignés au groupe avec succès.',
|
||||
'assigned_count' => $updatedCount,
|
||||
'group' => new ClientGroupResource($clientGroup),
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error assigning clients to group: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'client_group_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de l’assignation des clients au groupe.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreClientLocationRequest;
|
||||
use App\Http\Requests\UpdateClientLocationRequest;
|
||||
use App\Http\Resources\Client\ClientLocationResource;
|
||||
use App\Http\Resources\Client\ClientLocationCollection;
|
||||
use App\Repositories\ClientLocationRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ClientLocationController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ClientLocationRepositoryInterface $clientLocationRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of client locations.
|
||||
*/
|
||||
public function index(Request $request)
|
||||
{
|
||||
try {
|
||||
$filters = $request->only(['client_id', 'is_default', 'search']);
|
||||
$perPage = (int) $request->get('per_page', 10);
|
||||
|
||||
$clientLocations = $this->clientLocationRepository->getPaginated($filters, $perPage);
|
||||
return new ClientLocationCollection($clientLocations);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching client locations: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des lieux clients.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created client location.
|
||||
*/
|
||||
public function store(StoreClientLocationRequest $request): ClientLocationResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$clientLocation = $this->clientLocationRepository->create($request->validated());
|
||||
return new ClientLocationResource($clientLocation);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating client location: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création du lieu client.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified client id.
|
||||
*/
|
||||
public function getLocationsByClient(string $id)
|
||||
{
|
||||
try {
|
||||
$clientLocations = $this->clientLocationRepository->getByClientId((int)$id);
|
||||
return ClientLocationResource::collection($clientLocations);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching client location: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'client_location_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du lieu client.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified client location.
|
||||
*/
|
||||
public function show(string $id): ClientLocationResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$clientLocation = $this->clientLocationRepository->find($id);
|
||||
|
||||
if (!$clientLocation) {
|
||||
return response()->json([
|
||||
'message' => 'Lieu client non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new ClientLocationResource($clientLocation);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching client location: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'client_location_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du lieu client.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified client location.
|
||||
*/
|
||||
public function update(UpdateClientLocationRequest $request, string $id): ClientLocationResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->clientLocationRepository->update($id, $request->validated());
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Lieu client non trouvé ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$clientLocation = $this->clientLocationRepository->find($id);
|
||||
return new ClientLocationResource($clientLocation);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating client location: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'client_location_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du lieu client.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified client location.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->clientLocationRepository->delete($id);
|
||||
|
||||
if (!$deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Lieu client non trouvé ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Lieu client supprimé avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting client location: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'client_location_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression du lieu client.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
214
thanasoft-back/app/Http/Controllers/Api/ContactController.php
Normal file
214
thanasoft-back/app/Http/Controllers/Api/ContactController.php
Normal file
@ -0,0 +1,214 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreContactRequest;
|
||||
use App\Http\Requests\UpdateContactRequest;
|
||||
use App\Http\Resources\Contact\ContactResource;
|
||||
use App\Http\Resources\Contact\ContactCollection;
|
||||
use App\Repositories\ContactRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ContactController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ContactRepositoryInterface $contactRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of contacts.
|
||||
*/
|
||||
public function index(): ContactCollection
|
||||
{
|
||||
try {
|
||||
$perPage = request('per_page', 15);
|
||||
$filters = [
|
||||
'search' => request('search'),
|
||||
'is_primary' => request('is_primary'),
|
||||
'client_id' => request('client_id'),
|
||||
'sort_by' => request('sort_by', 'created_at'),
|
||||
'sort_direction' => request('sort_direction', 'desc'),
|
||||
];
|
||||
|
||||
$contacts = $this->contactRepository->paginate($perPage, $filters);
|
||||
|
||||
return new ContactCollection($contacts);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching contacts: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des contacts.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created contact.
|
||||
*/
|
||||
public function store(StoreContactRequest $request): ContactResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$contact = $this->contactRepository->create($request->validated());
|
||||
return new ContactResource($contact);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating contact: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création du contact.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified contact.
|
||||
*/
|
||||
public function show(string $id): ContactResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$contact = $this->contactRepository->find($id);
|
||||
|
||||
if (!$contact) {
|
||||
return response()->json([
|
||||
'message' => 'Contact non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new ContactResource($contact);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching contact: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'contact_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du contact.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified contact.
|
||||
*/
|
||||
public function update(UpdateContactRequest $request, string $id): ContactResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->contactRepository->update($id, $request->validated());
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Contact non trouvé ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$contact = $this->contactRepository->find($id);
|
||||
return new ContactResource($contact);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating contact: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'contact_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du contact.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified contact.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->contactRepository->delete($id);
|
||||
|
||||
if (!$deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Contact non trouvé ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Contact supprimé avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting contact: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'contact_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression du contact.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getContactsByClient(string $clientId): JsonResponse
|
||||
{
|
||||
try {
|
||||
$intId = (int) $clientId;
|
||||
$contacts = $this->contactRepository->getByClientId($intId);
|
||||
return response()->json([
|
||||
'data' => ContactResource::collection($contacts),
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching contacts by client: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'client_id' => $clientId,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des contacts du client.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function getContactsByFournisseur(string $fournisseurId): JsonResponse
|
||||
{
|
||||
try {
|
||||
$intId = (int) $fournisseurId;
|
||||
$contacts = $this->contactRepository->getByFournisseurId($intId);
|
||||
return response()->json([
|
||||
'data' => ContactResource::collection($contacts),
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching contacts by fournisseur: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'fournisseur_id' => $fournisseurId,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des contacts du fournisseur.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
144
thanasoft-back/app/Http/Controllers/Api/ConvoyController.php
Normal file
144
thanasoft-back/app/Http/Controllers/Api/ConvoyController.php
Normal file
@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreConvoyRequest;
|
||||
use App\Http\Requests\UpdateConvoyRequest;
|
||||
use App\Http\Resources\Convoy\ConvoyResource;
|
||||
use App\Repositories\ConvoyRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ConvoyController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ConvoyRepositoryInterface $convoyRepository
|
||||
) {
|
||||
}
|
||||
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$convoys = $this->convoyRepository->paginate(
|
||||
(int) $request->integer('per_page', 15),
|
||||
$request->only(['search', 'status', 'convoy_type', 'vehicle_id', 'deceased_id', 'sort_by', 'sort_direction'])
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'data' => ConvoyResource::collection($convoys->items()),
|
||||
'meta' => [
|
||||
'current_page' => $convoys->currentPage(),
|
||||
'last_page' => $convoys->lastPage(),
|
||||
'per_page' => $convoys->perPage(),
|
||||
'total' => $convoys->total(),
|
||||
],
|
||||
'status' => 'success',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching convoys: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'An error occurred while fetching convoys.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function store(StoreConvoyRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$convoy = $this->convoyRepository->create($request->validated());
|
||||
|
||||
return response()->json([
|
||||
'data' => new ConvoyResource($convoy->load(['deceased', 'client', 'vehicle', 'departureLocation'])),
|
||||
'message' => 'Convoy created successfully.',
|
||||
'status' => 'success',
|
||||
], 201);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating convoy: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'An error occurred while creating the convoy.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function show(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$convoy = $this->convoyRepository->find((int) $id);
|
||||
|
||||
if (! $convoy) {
|
||||
return response()->json(['message' => 'Convoy not found.'], 404);
|
||||
}
|
||||
|
||||
$convoy->load(['deceased', 'client', 'vehicle', 'departureLocation']);
|
||||
|
||||
return response()->json([
|
||||
'data' => new ConvoyResource($convoy),
|
||||
'status' => 'success',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching convoy: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'An error occurred while fetching the convoy.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function update(UpdateConvoyRequest $request, string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->convoyRepository->update((int) $id, $request->validated());
|
||||
|
||||
if (! $updated) {
|
||||
return response()->json(['message' => 'Convoy not found or update failed.'], 404);
|
||||
}
|
||||
|
||||
$convoy = $this->convoyRepository->find((int) $id);
|
||||
|
||||
return response()->json([
|
||||
'data' => new ConvoyResource($convoy->load(['deceased', 'client', 'vehicle', 'departureLocation'])),
|
||||
'message' => 'Convoy updated successfully.',
|
||||
'status' => 'success',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating convoy: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'An error occurred while updating the convoy.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->convoyRepository->delete((int) $id);
|
||||
|
||||
if (! $deleted) {
|
||||
return response()->json(['message' => 'Convoy not found or delete failed.'], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Convoy deleted successfully.',
|
||||
'status' => 'success',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting convoy: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'An error occurred while deleting the convoy.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
183
thanasoft-back/app/Http/Controllers/Api/DeceasedController.php
Normal file
183
thanasoft-back/app/Http/Controllers/Api/DeceasedController.php
Normal file
@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreDeceasedRequest;
|
||||
use App\Http\Requests\UpdateDeceasedRequest;
|
||||
use App\Http\Resources\Deceased\DeceasedResource;
|
||||
use App\Http\Resources\Deceased\DeceasedCollection;
|
||||
use App\Models\Deceased;
|
||||
use App\Repositories\DeceasedRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class DeceasedController extends Controller
|
||||
{
|
||||
/**
|
||||
* @var DeceasedRepositoryInterface
|
||||
*/
|
||||
protected $deceasedRepository;
|
||||
|
||||
/**
|
||||
* DeceasedController constructor.
|
||||
*
|
||||
* @param DeceasedRepositoryInterface $deceasedRepository
|
||||
*/
|
||||
public function __construct(DeceasedRepositoryInterface $deceasedRepository)
|
||||
{
|
||||
$this->deceasedRepository = $deceasedRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$filters = $request->only([
|
||||
'search',
|
||||
'start_date',
|
||||
'end_date',
|
||||
'sort_by',
|
||||
'sort_order'
|
||||
]);
|
||||
|
||||
$perPage = $request->input('per_page', 15);
|
||||
|
||||
$deceased = $this->deceasedRepository->getAllPaginated($filters, $perPage);
|
||||
|
||||
return response()->json(new DeceasedCollection($deceased));
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching deceased list: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des défunts.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(StoreDeceasedRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$validated = $request->validated();
|
||||
|
||||
$deceased = $this->deceasedRepository->create($validated);
|
||||
|
||||
return response()->json(new DeceasedResource($deceased), Response::HTTP_CREATED);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating deceased: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création du défunt.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deceased = $this->deceasedRepository->findById($id);
|
||||
|
||||
return response()->json(new DeceasedResource($deceased));
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching deceased details: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Défunt non trouvé ou une erreur est survenue.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(UpdateDeceasedRequest $request, int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deceased = $this->deceasedRepository->findById($id);
|
||||
|
||||
$validated = $request->validated();
|
||||
|
||||
$updatedDeceased = $this->deceasedRepository->update($deceased, $validated);
|
||||
|
||||
return response()->json(new DeceasedResource($updatedDeceased));
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating deceased: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du défunt.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deceased = $this->deceasedRepository->findById($id);
|
||||
|
||||
$this->deceasedRepository->delete($deceased);
|
||||
|
||||
return response()->json(null, Response::HTTP_NO_CONTENT);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting deceased: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression du défunt.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Search deceased by name or other criteria.
|
||||
*/
|
||||
public function searchBy(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$search = $request->get('search', '');
|
||||
|
||||
if (empty($search)) {
|
||||
return response()->json([
|
||||
'message' => 'Le paramètre "search" est requis.',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$deceased = $this->deceasedRepository->searchByName($search);
|
||||
|
||||
return response()->json([
|
||||
'data' => $deceased,
|
||||
'count' => $deceased->count(),
|
||||
'message' => $deceased->count() > 0
|
||||
? 'Défunts trouvés avec succès.'
|
||||
: 'Aucun défunt trouvé.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error searching deceased by name: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'search_term' => $search ?? '',
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la recherche des défunts.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreDeceasedDocumentRequest;
|
||||
use App\Http\Requests\UpdateDeceasedDocumentRequest;
|
||||
use App\Http\Resources\Deceased\DeceasedDocumentResource;
|
||||
use App\Http\Resources\Deceased\DeceasedDocumentCollection;
|
||||
use App\Repositories\DeceasedDocumentRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class DeceasedDocumentController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly DeceasedDocumentRepositoryInterface $deceasedDocumentRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of deceased documents.
|
||||
*/
|
||||
public function index(Request $request): DeceasedDocumentCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = $request->get('per_page', 15);
|
||||
$filters = [
|
||||
'search' => $request->get('search'),
|
||||
'deceased_id' => $request->get('deceased_id'),
|
||||
'doc_type' => $request->get('doc_type'),
|
||||
'file_id' => $request->get('file_id'),
|
||||
'sort_by' => $request->get('sort_by', 'created_at'),
|
||||
'sort_direction' => $request->get('sort_direction', 'desc'),
|
||||
];
|
||||
|
||||
// Remove null filters
|
||||
$filters = array_filter($filters, function ($value) {
|
||||
return $value !== null && $value !== '';
|
||||
});
|
||||
|
||||
$documents = $this->deceasedDocumentRepository->paginate($perPage, $filters);
|
||||
|
||||
return new DeceasedDocumentCollection($documents);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Erreur lors de la récupération des documents du défunt: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des documents du défunt.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get documents by deceased ID.
|
||||
*/
|
||||
public function byDeceased(string $deceasedId): DeceasedDocumentCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$documents = $this->deceasedDocumentRepository->getByDeceasedId((int) $deceasedId);
|
||||
return new DeceasedDocumentCollection($documents);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Erreur lors de la récupération des documents par défunt: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'deceased_id' => $deceasedId,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des documents du défunt.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get documents by document type.
|
||||
*/
|
||||
public function byDocType(Request $request): DeceasedDocumentCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$docType = $request->get('doc_type');
|
||||
|
||||
if (!$docType) {
|
||||
return response()->json([
|
||||
'message' => 'Le paramètre doc_type est requis.',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$documents = $this->deceasedDocumentRepository->getByDocType($docType);
|
||||
return new DeceasedDocumentCollection($documents);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Erreur lors de la récupération des documents par type: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'doc_type' => $request->get('doc_type'),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des documents par type.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get documents by file ID.
|
||||
*/
|
||||
public function byFile(string $fileId): DeceasedDocumentCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$documents = $this->deceasedDocumentRepository->getByFileId((int) $fileId);
|
||||
return new DeceasedDocumentCollection($documents);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Erreur lors de la récupération des documents par fichier: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'file_id' => $fileId,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des documents par fichier.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search documents by various criteria.
|
||||
*/
|
||||
public function search(Request $request): DeceasedDocumentCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$criteria = [
|
||||
'deceased_id' => $request->get('deceased_id'),
|
||||
'doc_type' => $request->get('doc_type'),
|
||||
'file_id' => $request->get('file_id'),
|
||||
'generated_from' => $request->get('generated_from'),
|
||||
'generated_to' => $request->get('generated_to'),
|
||||
];
|
||||
|
||||
// Remove null criteria
|
||||
$criteria = array_filter($criteria, function ($value) {
|
||||
return $value !== null && $value !== '';
|
||||
});
|
||||
|
||||
$documents = $this->deceasedDocumentRepository->search($criteria);
|
||||
return new DeceasedDocumentCollection($documents);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Erreur lors de la recherche de documents: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'criteria' => $request->all(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la recherche de documents.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created deceased document.
|
||||
*/
|
||||
public function store(StoreDeceasedDocumentRequest $request): DeceasedDocumentResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$document = $this->deceasedDocumentRepository->create($request->validated());
|
||||
return new DeceasedDocumentResource($document);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Erreur lors de la création du document du défunt: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création du document du défunt.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified deceased document.
|
||||
*/
|
||||
public function show(string $id): DeceasedDocumentResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$document = $this->deceasedDocumentRepository->find($id);
|
||||
|
||||
if (!$document) {
|
||||
return response()->json([
|
||||
'message' => 'Document du défunt non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new DeceasedDocumentResource($document);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Erreur lors de la récupération du document du défunt: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'document_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du document du défunt.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified deceased document.
|
||||
*/
|
||||
public function update(UpdateDeceasedDocumentRequest $request, string $id): DeceasedDocumentResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->deceasedDocumentRepository->update($id, $request->validated());
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Document du défunt non trouvé ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$document = $this->deceasedDocumentRepository->find($id);
|
||||
return new DeceasedDocumentResource($document);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Erreur lors de la mise à jour du document du défunt: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'document_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du document du défunt.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified deceased document.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->deceasedDocumentRepository->delete($id);
|
||||
|
||||
if (!$deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Document du défunt non trouvé ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Document du défunt supprimé avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Erreur lors de la suppression du document du défunt: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'document_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression du document du défunt.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
423
thanasoft-back/app/Http/Controllers/Api/EmployeeController.php
Normal file
423
thanasoft-back/app/Http/Controllers/Api/EmployeeController.php
Normal file
@ -0,0 +1,423 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreEmployeeRequest;
|
||||
use App\Http\Requests\UpdateEmployeeRequest;
|
||||
use App\Http\Resources\Employee\EmployeeResource;
|
||||
use App\Http\Resources\Employee\EmployeeCollection;
|
||||
use App\Http\Resources\Intervention\InterventionResource;
|
||||
use App\Repositories\EmployeeRepositoryInterface;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class EmployeeController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EmployeeRepositoryInterface $employeeRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of employees (paginated).
|
||||
*/
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = (int) $request->get('per_page', 15);
|
||||
|
||||
$filters = [
|
||||
'search' => $request->get('search'),
|
||||
'active' => $request->get('active'),
|
||||
'sort_by' => $request->get('sort_by', 'last_name'),
|
||||
'sort_direction' => $request->get('sort_direction', 'asc'),
|
||||
];
|
||||
|
||||
// Remove null filters
|
||||
$filters = array_filter($filters, function ($value) {
|
||||
return $value !== null && $value !== '';
|
||||
});
|
||||
|
||||
$result = $this->employeeRepository->getPaginated($perPage, $filters);
|
||||
|
||||
return response()->json([
|
||||
'data' => new EmployeeCollection($result['employees']),
|
||||
'pagination' => $result['pagination'],
|
||||
'message' => 'Employés récupérés avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching employees: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des employés.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display paginated employees.
|
||||
*/
|
||||
public function paginated(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = (int) $request->get('per_page', 15);
|
||||
$result = $this->employeeRepository->getPaginated($perPage, []);
|
||||
|
||||
return response()->json([
|
||||
'data' => new EmployeeCollection($result['employees']),
|
||||
'pagination' => $result['pagination'],
|
||||
'message' => 'Employés récupérés avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching paginated employees: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des employés.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active employees only.
|
||||
*/
|
||||
public function active(): EmployeeCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$employees = $this->employeeRepository->getActive();
|
||||
return new EmployeeCollection($employees);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching active employees: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des employés actifs.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get employees with thanatopractitioner data.
|
||||
*/
|
||||
public function withThanatopractitioner(): EmployeeCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$employees = $this->employeeRepository->getWithThanatopractitioner();
|
||||
return new EmployeeCollection($employees);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching employees with thanatopractitioner: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des employés avec données thanatopractitioner.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get employee statistics.
|
||||
*/
|
||||
public function statistics(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$statistics = $this->employeeRepository->getStatistics();
|
||||
|
||||
return response()->json([
|
||||
'data' => $statistics,
|
||||
'message' => 'Statistiques des employés récupérées avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching employee statistics: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des statistiques.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created employee.
|
||||
*/
|
||||
public function store(StoreEmployeeRequest $request): EmployeeResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$employee = $this->employeeRepository->create($request->validated());
|
||||
return new EmployeeResource($employee);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating employee: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création de l\'employé.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified employee.
|
||||
*/
|
||||
public function show(string $id): EmployeeResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$employee = $this->employeeRepository->find($id);
|
||||
|
||||
if (!$employee) {
|
||||
return response()->json([
|
||||
'message' => 'Employé non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new EmployeeResource($employee);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching employee: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'employee_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération de l\'employé.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the intervention agenda for a specific employee.
|
||||
*/
|
||||
public function agenda(Request $request, string $id): JsonResponse
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'month' => ['nullable', 'date_format:Y-m'],
|
||||
'start_date' => ['nullable', 'date'],
|
||||
'end_date' => ['nullable', 'date', 'after_or_equal:start_date'],
|
||||
'status' => ['nullable', 'string'],
|
||||
'type' => ['nullable', 'string'],
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'message' => 'Paramètres d\'agenda invalides.',
|
||||
'errors' => $validator->errors(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
try {
|
||||
$employee = $this->employeeRepository->find($id);
|
||||
|
||||
if (!$employee) {
|
||||
return response()->json([
|
||||
'message' => 'Employé non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$employee->load('thanatopractitioner');
|
||||
|
||||
if (!$employee->thanatopractitioner) {
|
||||
return response()->json([
|
||||
'message' => 'Aucun agenda disponible pour cet employé.',
|
||||
'employee' => [
|
||||
'id' => $employee->id,
|
||||
'full_name' => $employee->full_name,
|
||||
'job_title' => $employee->job_title,
|
||||
],
|
||||
'filters' => [
|
||||
'month' => $request->input('month'),
|
||||
'start_date' => $request->input('start_date'),
|
||||
'end_date' => $request->input('end_date'),
|
||||
'status' => $request->input('status'),
|
||||
'type' => $request->input('type'),
|
||||
],
|
||||
'data' => [],
|
||||
'meta' => [
|
||||
'total' => 0,
|
||||
'status_summary' => [],
|
||||
],
|
||||
], 200);
|
||||
}
|
||||
|
||||
[$startDate, $endDate] = $this->resolveAgendaPeriod(
|
||||
$request->input('month'),
|
||||
$request->input('start_date'),
|
||||
$request->input('end_date')
|
||||
);
|
||||
|
||||
$interventions = $employee->thanatopractitioner
|
||||
->interventions()
|
||||
->with([
|
||||
'client',
|
||||
'deceased',
|
||||
'location',
|
||||
'quote',
|
||||
'practitioners.employee',
|
||||
])
|
||||
->when($startDate, function ($query) use ($startDate) {
|
||||
$query->where('scheduled_at', '>=', $startDate);
|
||||
})
|
||||
->when($endDate, function ($query) use ($endDate) {
|
||||
$query->where('scheduled_at', '<=', $endDate);
|
||||
})
|
||||
->when($request->filled('status'), function ($query) use ($request) {
|
||||
$query->where('status', $request->string('status'));
|
||||
})
|
||||
->when($request->filled('type'), function ($query) use ($request) {
|
||||
$query->where('type', $request->string('type'));
|
||||
})
|
||||
->orderBy('scheduled_at')
|
||||
->get();
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Agenda employé récupéré avec succès.',
|
||||
'employee' => [
|
||||
'id' => $employee->id,
|
||||
'full_name' => $employee->full_name,
|
||||
'job_title' => $employee->job_title,
|
||||
'thanatopractitioner_id' => $employee->thanatopractitioner->id,
|
||||
],
|
||||
'filters' => [
|
||||
'month' => $request->input('month'),
|
||||
'start_date' => $startDate?->toDateTimeString(),
|
||||
'end_date' => $endDate?->toDateTimeString(),
|
||||
'status' => $request->input('status'),
|
||||
'type' => $request->input('type'),
|
||||
],
|
||||
'data' => InterventionResource::collection($interventions)->resolve(),
|
||||
'meta' => [
|
||||
'total' => $interventions->count(),
|
||||
'status_summary' => $interventions
|
||||
->groupBy('status')
|
||||
->map(fn ($group) => $group->count())
|
||||
->toArray(),
|
||||
],
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching employee agenda: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'employee_id' => $id,
|
||||
'filters' => $request->all(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération de l\'agenda employé.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified employee.
|
||||
*/
|
||||
public function update(UpdateEmployeeRequest $request, string $id): EmployeeResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->employeeRepository->update($id, $request->validated());
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Employé non trouvé ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$employee = $this->employeeRepository->find($id);
|
||||
return new EmployeeResource($employee);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating employee: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'employee_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour de l\'employé.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified employee.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->employeeRepository->delete($id);
|
||||
|
||||
if (!$deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Employé non trouvé ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Employé supprimé avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting employee: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'employee_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression de l\'employé.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the requested agenda period.
|
||||
*
|
||||
* Defaults to the current month when no explicit range is provided.
|
||||
*
|
||||
* @return array{0: \Carbon\Carbon, 1: \Carbon\Carbon}
|
||||
*/
|
||||
private function resolveAgendaPeriod(?string $month, ?string $startDate, ?string $endDate): array
|
||||
{
|
||||
if ($month) {
|
||||
$reference = Carbon::createFromFormat('Y-m', $month)->startOfMonth();
|
||||
|
||||
return [$reference->copy()->startOfMonth(), $reference->copy()->endOfMonth()];
|
||||
}
|
||||
|
||||
if ($startDate || $endDate) {
|
||||
return [
|
||||
$startDate ? Carbon::parse($startDate)->startOfDay() : Carbon::now()->startOfMonth(),
|
||||
$endDate ? Carbon::parse($endDate)->endOfDay() : Carbon::now()->endOfMonth(),
|
||||
];
|
||||
}
|
||||
|
||||
return [Carbon::now()->startOfMonth(), Carbon::now()->endOfMonth()];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,438 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\FileAttachment\FileAttachmentResource;
|
||||
use App\Models\File;
|
||||
use App\Models\FileAttachment;
|
||||
use App\Models\Intervention;
|
||||
use App\Models\Client;
|
||||
use App\Models\Deceased;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class FileAttachmentController extends Controller
|
||||
{
|
||||
/**
|
||||
* Attach a file to a model (Intervention, Client, Deceased, etc.)
|
||||
*/
|
||||
public function attach(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'file_id' => 'required|exists:files,id',
|
||||
'attachable_type' => 'required|string|in:App\Models\Intervention,App\Models\Client,App\Models\Deceased',
|
||||
'attachable_id' => 'required|integer',
|
||||
'label' => 'nullable|string|max:255',
|
||||
'sort_order' => 'nullable|integer|min:0',
|
||||
]);
|
||||
|
||||
// Verify the attachable model exists
|
||||
$attachableModel = $this->getAttachableModel($request->attachable_type, $request->attachable_id);
|
||||
if (!$attachableModel) {
|
||||
return response()->json([
|
||||
'message' => 'Le modèle cible n\'existe pas.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
// Check if file is already attached to this model
|
||||
$existingAttachment = FileAttachment::where('file_id', $request->file_id)
|
||||
->where('attachable_type', $request->attachable_type)
|
||||
->where('attachable_id', $request->attachable_id)
|
||||
->first();
|
||||
|
||||
if ($existingAttachment) {
|
||||
return response()->json([
|
||||
'message' => 'Ce fichier est déjà attaché à cet élément.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
// Create the attachment
|
||||
$attachment = FileAttachment::create([
|
||||
'file_id' => $request->file_id,
|
||||
'attachable_type' => $request->attachable_type,
|
||||
'attachable_id' => $request->attachable_id,
|
||||
'label' => $request->label,
|
||||
'sort_order' => $request->sort_order ?? 0,
|
||||
]);
|
||||
|
||||
// Load relationships for response
|
||||
$attachment->load('file');
|
||||
|
||||
DB::commit();
|
||||
|
||||
return response()->json([
|
||||
'data' => new FileAttachmentResource($attachment),
|
||||
'message' => 'Fichier attaché avec succès.',
|
||||
], 201);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error attaching file: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'request_data' => $request->all(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de l\'attachement du fichier.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach a file from a model
|
||||
*/
|
||||
public function detach(Request $request, string $attachmentId): JsonResponse
|
||||
{
|
||||
try {
|
||||
$attachment = FileAttachment::find($attachmentId);
|
||||
|
||||
if (!$attachment) {
|
||||
return response()->json([
|
||||
'message' => 'Attachement de fichier non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
$attachment->delete();
|
||||
|
||||
DB::commit();
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Fichier détaché avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error detaching file: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'attachment_id' => $attachmentId,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors du détachement du fichier.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update file attachment metadata
|
||||
*/
|
||||
public function update(Request $request, string $attachmentId): JsonResponse
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'label' => 'nullable|string|max:255',
|
||||
'sort_order' => 'nullable|integer|min:0',
|
||||
]);
|
||||
|
||||
$attachment = FileAttachment::find($attachmentId);
|
||||
|
||||
if (!$attachment) {
|
||||
return response()->json([
|
||||
'message' => 'Attachement de fichier non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
$attachment->update($request->only(['label', 'sort_order']));
|
||||
$attachment->load('file');
|
||||
|
||||
DB::commit();
|
||||
|
||||
return response()->json([
|
||||
'data' => new FileAttachmentResource($attachment),
|
||||
'message' => 'Attachement de fichier mis à jour avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating file attachment: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'attachment_id' => $attachmentId,
|
||||
'request_data' => $request->all(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour de l\'attachement.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get files attached to a specific model
|
||||
*/
|
||||
public function getAttachedFiles(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'attachable_type' => 'required|string|in:App\Models\Intervention,App\Models\Client,App\Models\Deceased',
|
||||
'attachable_id' => 'required|integer',
|
||||
]);
|
||||
|
||||
$attachments = FileAttachment::where('attachable_type', $request->attachable_type)
|
||||
->where('attachable_id', $request->attachable_id)
|
||||
->with('file')
|
||||
->orderBy('sort_order')
|
||||
->orderBy('created_at')
|
||||
->get();
|
||||
|
||||
return response()->json([
|
||||
'data' => FileAttachmentResource::collection($attachments),
|
||||
'count' => $attachments->count(),
|
||||
'message' => 'Fichiers attachés récupérés avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error getting attached files: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'request_data' => $request->all(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des fichiers attachés.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get files attached to an intervention
|
||||
*/
|
||||
public function getInterventionFiles(Request $request, int $interventionId): JsonResponse
|
||||
{
|
||||
try {
|
||||
$intervention = Intervention::find($interventionId);
|
||||
|
||||
if (!$intervention) {
|
||||
return response()->json([
|
||||
'message' => 'Intervention non trouvée.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$attachments = $intervention->fileAttachments()->with('file')->get();
|
||||
|
||||
return response()->json([
|
||||
'data' => FileAttachmentResource::collection($attachments),
|
||||
'count' => $attachments->count(),
|
||||
'message' => 'Fichiers de l\'intervention récupérés avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error getting intervention files: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'intervention_id' => $interventionId,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des fichiers de l\'intervention.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get files attached to a client
|
||||
*/
|
||||
public function getClientFiles(Request $request, int $clientId): JsonResponse
|
||||
{
|
||||
try {
|
||||
$client = Client::find($clientId);
|
||||
|
||||
if (!$client) {
|
||||
return response()->json([
|
||||
'message' => 'Client non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$attachments = $client->fileAttachments()->with('file')->get();
|
||||
|
||||
return response()->json([
|
||||
'data' => FileAttachmentResource::collection($attachments),
|
||||
'count' => $attachments->count(),
|
||||
'message' => 'Fichiers du client récupérés avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error getting client files: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'client_id' => $clientId,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des fichiers du client.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get files attached to a deceased
|
||||
*/
|
||||
public function getDeceasedFiles(Request $request, int $deceasedId): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deceased = Deceased::find($deceasedId);
|
||||
|
||||
if (!$deceased) {
|
||||
return response()->json([
|
||||
'message' => 'Défunt non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$attachments = $deceased->fileAttachments()->with('file')->get();
|
||||
|
||||
return response()->json([
|
||||
'data' => FileAttachmentResource::collection($attachments),
|
||||
'count' => $attachments->count(),
|
||||
'message' => 'Fichiers du défunt récupérés avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error getting deceased files: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'deceased_id' => $deceasedId,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des fichiers du défunt.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach multiple files at once
|
||||
*/
|
||||
public function detachMultiple(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'attachment_ids' => 'required|array|min:1',
|
||||
'attachment_ids.*' => 'exists:file_attachments,id',
|
||||
]);
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
$deletedCount = FileAttachment::whereIn('id', $request->attachment_ids)->delete();
|
||||
|
||||
DB::commit();
|
||||
|
||||
return response()->json([
|
||||
'deleted_count' => $deletedCount,
|
||||
'message' => $deletedCount . ' fichier(s) détaché(s) avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error detaching multiple files: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'attachment_ids' => $request->attachment_ids ?? [],
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors du détachement des fichiers.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorder file attachments
|
||||
*/
|
||||
public function reorder(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'attachments' => 'required|array|min:1',
|
||||
'attachments.*.id' => 'required|exists:file_attachments,id',
|
||||
'attachments.*.sort_order' => 'required|integer|min:0',
|
||||
]);
|
||||
|
||||
DB::beginTransaction();
|
||||
|
||||
try {
|
||||
foreach ($request->attachments as $attachmentData) {
|
||||
FileAttachment::where('id', $attachmentData['id'])
|
||||
->update(['sort_order' => $attachmentData['sort_order']]);
|
||||
}
|
||||
|
||||
DB::commit();
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Ordre des fichiers mis à jour avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
DB::rollBack();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error reordering file attachments: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'attachments' => $request->attachments ?? [],
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la réorganisation des fichiers.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attachable model instance
|
||||
*/
|
||||
private function getAttachableModel(string $type, int $id): ?Model
|
||||
{
|
||||
return match ($type) {
|
||||
Intervention::class => Intervention::find($id),
|
||||
Client::class => Client::find($id),
|
||||
Deceased::class => Deceased::find($id),
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
444
thanasoft-back/app/Http/Controllers/Api/FileController.php
Normal file
444
thanasoft-back/app/Http/Controllers/Api/FileController.php
Normal file
@ -0,0 +1,444 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreFileRequest;
|
||||
use App\Http\Requests\UpdateFileRequest;
|
||||
use App\Http\Resources\File\FileResource;
|
||||
use App\Http\Resources\File\FileCollection;
|
||||
use App\Repositories\FileRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class FileController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly FileRepositoryInterface $fileRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of files.
|
||||
*/
|
||||
public function index(Request $request): FileCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = $request->get('per_page', 15);
|
||||
$filters = [
|
||||
'search' => $request->get('search'),
|
||||
'mime_type' => $request->get('mime_type'),
|
||||
'uploaded_by' => $request->get('uploaded_by'),
|
||||
'category' => $request->get('category'),
|
||||
'client_id' => $request->get('client_id'),
|
||||
'date_from' => $request->get('date_from'),
|
||||
'date_to' => $request->get('date_to'),
|
||||
'sort_by' => $request->get('sort_by', 'uploaded_at'),
|
||||
'sort_direction' => $request->get('sort_direction', 'desc'),
|
||||
];
|
||||
|
||||
// Remove null filters
|
||||
$filters = array_filter($filters, function ($value) {
|
||||
return $value !== null && $value !== '';
|
||||
});
|
||||
|
||||
$files = $this->fileRepository->paginate($perPage, $filters);
|
||||
|
||||
return new FileCollection($files);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching files: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des fichiers.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly uploaded file.
|
||||
*/
|
||||
public function store(StoreFileRequest $request): FileResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$validatedData = $request->validated();
|
||||
$file = $request->file('file');
|
||||
|
||||
// Generate organized storage path
|
||||
$storagePath = $this->generateOrganizedPath(
|
||||
$validatedData['category'],
|
||||
$validatedData['client_id'] ?? null,
|
||||
$validatedData['subcategory'] ?? null,
|
||||
$validatedData['file_name']
|
||||
);
|
||||
|
||||
// Store the file
|
||||
$storedFilePath = $file->store($storagePath, 'public');
|
||||
|
||||
// Calculate SHA256 hash
|
||||
$hash = hash_file('sha256', $file->path());
|
||||
|
||||
// Prepare data for database
|
||||
$fileData = array_merge($validatedData, [
|
||||
'storage_uri' => $storedFilePath,
|
||||
'sha256' => $hash,
|
||||
'uploaded_by' => $request->user()->id,
|
||||
'uploaded_at' => now(),
|
||||
]);
|
||||
|
||||
$createdFile = $this->fileRepository->create($fileData);
|
||||
|
||||
return new FileResource($createdFile);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error uploading file: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'user_id' => $request->user()->id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de l\'upload du fichier.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified file.
|
||||
*/
|
||||
public function show(string $id): FileResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$file = $this->fileRepository->find($id);
|
||||
|
||||
if (!$file) {
|
||||
return response()->json([
|
||||
'message' => 'Fichier non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new FileResource($file);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching file: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'file_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du fichier.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified file metadata.
|
||||
*/
|
||||
public function update(UpdateFileRequest $request, string $id): FileResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$file = $this->fileRepository->find($id);
|
||||
|
||||
if (!$file) {
|
||||
return response()->json([
|
||||
'message' => 'Fichier non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$validatedData = $request->validated();
|
||||
|
||||
// If category or client changed, move the file
|
||||
if (isset($validatedData['category']) || isset($validatedData['client_id']) || isset($validatedData['subcategory'])) {
|
||||
$newStoragePath = $this->generateOrganizedPath(
|
||||
$validatedData['category'] ?? $this->extractCategoryFromPath($file->storage_uri),
|
||||
$validatedData['client_id'] ?? $this->extractClientFromPath($file->storage_uri),
|
||||
$validatedData['subcategory'] ?? $this->extractSubcategoryFromPath($file->storage_uri),
|
||||
$file->file_name
|
||||
);
|
||||
|
||||
if ($newStoragePath !== $file->storage_uri) {
|
||||
// Move file to new location
|
||||
Storage::disk('public')->move($file->storage_uri, $newStoragePath);
|
||||
$validatedData['storage_uri'] = $newStoragePath;
|
||||
}
|
||||
}
|
||||
|
||||
$updated = $this->fileRepository->update($id, $validatedData);
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Fichier non trouvé ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$updatedFile = $this->fileRepository->find($id);
|
||||
return new FileResource($updatedFile);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating file: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'file_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du fichier.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified file.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$file = $this->fileRepository->find($id);
|
||||
|
||||
if (!$file) {
|
||||
return response()->json([
|
||||
'message' => 'Fichier non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
// Delete file from storage
|
||||
Storage::disk('public')->delete($file->storage_uri);
|
||||
|
||||
// Delete from database
|
||||
$deleted = $this->fileRepository->delete($id);
|
||||
|
||||
if (!$deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Fichier non trouvé ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Fichier supprimé avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting file: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'file_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression du fichier.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get files by category.
|
||||
*/
|
||||
public function byCategory(Request $request, string $category): FileCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = $request->get('per_page', 15);
|
||||
$files = $this->fileRepository->getByCategory($category, $perPage);
|
||||
|
||||
return new FileCollection($files);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching files by category: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'category' => $category,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des fichiers par catégorie.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get files by client.
|
||||
*/
|
||||
public function byClient(Request $request, int $clientId): FileCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = $request->get('per_page', 15);
|
||||
$files = $this->fileRepository->getByClient($clientId, $perPage);
|
||||
|
||||
return new FileCollection($files);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching files by client: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'client_id' => $clientId,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des fichiers du client.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get organized file structure.
|
||||
*/
|
||||
public function organized(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$organizedFiles = $this->fileRepository->getOrganizedFiles();
|
||||
|
||||
return response()->json([
|
||||
'data' => $organizedFiles,
|
||||
'message' => 'Structure de fichiers récupérée avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching organized files: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération de la structure de fichiers.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get storage statistics.
|
||||
*/
|
||||
public function stats(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$stats = $this->fileRepository->getStorageStats();
|
||||
|
||||
return response()->json([
|
||||
'data' => $stats,
|
||||
'message' => 'Statistiques de stockage récupérées avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching storage stats: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des statistiques.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download a file.
|
||||
*/
|
||||
public function download(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$file = $this->fileRepository->find($id);
|
||||
|
||||
if (!$file) {
|
||||
return response()->json([
|
||||
'message' => 'Fichier non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
if (!Storage::disk('public')->exists($file->storage_uri)) {
|
||||
return response()->json([
|
||||
'message' => 'Fichier physique non trouvé sur le stockage.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$downloadUrl = Storage::disk('public')->url($file->storage_uri);
|
||||
|
||||
return response()->json([
|
||||
'data' => [
|
||||
'download_url' => $downloadUrl,
|
||||
'file_name' => $file->file_name,
|
||||
'mime_type' => $file->mime_type,
|
||||
],
|
||||
'message' => 'URL de téléchargement générée avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error generating download URL: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'file_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la génération de l\'URL de téléchargement.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate organized storage path.
|
||||
*/
|
||||
private function generateOrganizedPath(string $category, ?int $clientId, ?string $subcategory, string $fileName): string
|
||||
{
|
||||
$pathParts = [];
|
||||
|
||||
if ($clientId) {
|
||||
$pathParts[] = 'client';
|
||||
$pathParts[] = $clientId;
|
||||
} else {
|
||||
$pathParts[] = 'general';
|
||||
}
|
||||
|
||||
$pathParts[] = $category;
|
||||
|
||||
if ($subcategory) {
|
||||
$pathParts[] = Str::slug($subcategory);
|
||||
} else {
|
||||
$pathParts[] = 'files';
|
||||
}
|
||||
|
||||
// Add timestamp to avoid conflicts
|
||||
$timestamp = now()->format('Y-m-d_H-i-s');
|
||||
$extension = pathinfo($fileName, PATHINFO_EXTENSION);
|
||||
$basename = pathinfo($fileName, PATHINFO_FILENAME);
|
||||
$safeFilename = Str::slug($basename) . '_' . $timestamp . '.' . $extension;
|
||||
|
||||
$pathParts[] = $safeFilename;
|
||||
|
||||
return implode('/', $pathParts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract category from storage path.
|
||||
*/
|
||||
private function extractCategoryFromPath(string $storageUri): string
|
||||
{
|
||||
$pathParts = explode('/', $storageUri);
|
||||
return $pathParts[count($pathParts) - 3] ?? 'general';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract client ID from storage path.
|
||||
*/
|
||||
private function extractClientFromPath(string $storageUri): ?int
|
||||
{
|
||||
$pathParts = explode('/', $storageUri);
|
||||
if (count($pathParts) >= 4 && $pathParts[0] === 'client') {
|
||||
return (int) $pathParts[1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract subcategory from storage path.
|
||||
*/
|
||||
private function extractSubcategoryFromPath(string $storageUri): ?string
|
||||
{
|
||||
$pathParts = explode('/', $storageUri);
|
||||
return $pathParts[count($pathParts) - 2] ?? null;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreFournisseurRequest;
|
||||
use App\Http\Requests\UpdateFournisseurRequest;
|
||||
use App\Http\Resources\Fournisseur\FournisseurResource;
|
||||
use App\Http\Resources\Fournisseur\FournisseurCollection;
|
||||
use App\Repositories\FournisseurRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class FournisseurController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly FournisseurRepositoryInterface $fournisseurRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of fournisseurs.
|
||||
*/
|
||||
public function index(Request $request): FournisseurCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = $request->get('per_page', 15);
|
||||
$filters = [
|
||||
'search' => $request->get('search'),
|
||||
'is_active' => $request->get('is_active'),
|
||||
'sort_by' => $request->get('sort_by', 'created_at'),
|
||||
'sort_direction' => $request->get('sort_direction', 'desc'),
|
||||
];
|
||||
|
||||
// Remove null filters
|
||||
$filters = array_filter($filters, function ($value) {
|
||||
return $value !== null && $value !== '';
|
||||
});
|
||||
|
||||
$fournisseurs = $this->fournisseurRepository->paginate($perPage, $filters);
|
||||
|
||||
return new FournisseurCollection($fournisseurs);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching fournisseurs: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des fournisseurs.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created fournisseur.
|
||||
*/
|
||||
public function store(StoreFournisseurRequest $request): FournisseurResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$fournisseur = $this->fournisseurRepository->create($request->validated());
|
||||
return new FournisseurResource($fournisseur);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating fournisseur: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création du fournisseur.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified fournisseur.
|
||||
*/
|
||||
public function show(string $id): FournisseurResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$fournisseur = $this->fournisseurRepository->find($id);
|
||||
|
||||
if (!$fournisseur) {
|
||||
return response()->json([
|
||||
'message' => 'Fournisseur non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new FournisseurResource($fournisseur);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching fournisseur: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'fournisseur_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du fournisseur.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function searchBy(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$name = $request->get('name', '');
|
||||
|
||||
if (empty($name)) {
|
||||
return response()->json([
|
||||
'message' => 'Le paramètre "name" est requis.',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$fournisseurs = $this->fournisseurRepository->searchByName($name);
|
||||
|
||||
return response()->json([
|
||||
'data' => $fournisseurs,
|
||||
'count' => $fournisseurs->count(),
|
||||
'message' => $fournisseurs->count() > 0
|
||||
? 'Fournisseurs trouvés avec succès.'
|
||||
: 'Aucun fournisseur trouvé.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error searching fournisseurs by name: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'search_term' => $name,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la recherche des fournisseurs.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified fournisseur.
|
||||
*/
|
||||
public function update(UpdateFournisseurRequest $request, string $id): FournisseurResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->fournisseurRepository->update($id, $request->validated());
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Fournisseur non trouvé ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$fournisseur = $this->fournisseurRepository->find($id);
|
||||
return new FournisseurResource($fournisseur);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating fournisseur: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'fournisseur_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du fournisseur.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified fournisseur.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->fournisseurRepository->delete($id);
|
||||
|
||||
if (!$deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Fournisseur non trouvé ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Fournisseur supprimé avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting fournisseur: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'fournisseur_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression du fournisseur.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreGoodsReceiptRequest;
|
||||
use App\Http\Requests\UpdateGoodsReceiptRequest;
|
||||
use App\Http\Resources\GoodsReceiptResource;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Repositories\GoodsReceiptRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class GoodsReceiptController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly GoodsReceiptRepositoryInterface $goodsReceiptRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of goods receipts.
|
||||
*/
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$goodsReceipts = $this->goodsReceiptRepository->all();
|
||||
return response()->json([
|
||||
'data' => GoodsReceiptResource::collection($goodsReceipts),
|
||||
'status' => 'success'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching goods receipts: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des réceptions de marchandises.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created goods receipt.
|
||||
*/
|
||||
public function store(StoreGoodsReceiptRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$payload = $request->validated();
|
||||
|
||||
if (empty($payload['lines']) && !empty($payload['purchase_order_id'])) {
|
||||
$purchaseOrder = PurchaseOrder::query()
|
||||
->with('lines')
|
||||
->find($payload['purchase_order_id']);
|
||||
|
||||
if ($purchaseOrder) {
|
||||
$payload['lines'] = $purchaseOrder->lines
|
||||
->filter(fn($line) => !empty($line->product_id))
|
||||
->map(fn($line) => [
|
||||
'product_id' => (int) $line->product_id,
|
||||
'packaging_id' => null,
|
||||
'packages_qty_received' => null,
|
||||
'units_qty_received' => (float) $line->quantity,
|
||||
'qty_received_base' => (float) $line->quantity,
|
||||
'unit_price' => (float) $line->unit_price,
|
||||
'unit_price_per_package' => null,
|
||||
'tva_rate_id' => null,
|
||||
])
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
}
|
||||
|
||||
$goodsReceipt = $this->goodsReceiptRepository->create($payload);
|
||||
return response()->json([
|
||||
'data' => new GoodsReceiptResource($goodsReceipt),
|
||||
'message' => 'Réception de marchandise créée avec succès.',
|
||||
'status' => 'success'
|
||||
], 201);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating goods receipt: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création de la réception de marchandise.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified goods receipt.
|
||||
*/
|
||||
public function show(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$goodsReceipt = $this->goodsReceiptRepository->find((int) $id);
|
||||
if (!$goodsReceipt) {
|
||||
return response()->json(['message' => 'Réception de marchandise non trouvée.'], 404);
|
||||
}
|
||||
return response()->json([
|
||||
'data' => new GoodsReceiptResource($goodsReceipt),
|
||||
'status' => 'success'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching goods receipt: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération de la réception de marchandise.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified goods receipt.
|
||||
*/
|
||||
public function update(UpdateGoodsReceiptRequest $request, string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->goodsReceiptRepository->update((int) $id, $request->validated());
|
||||
if (!$updated) {
|
||||
return response()->json(['message' => 'Réception de marchandise non trouvée ou échec de la mise à jour.'], 404);
|
||||
}
|
||||
$goodsReceipt = $this->goodsReceiptRepository->find((int) $id);
|
||||
return response()->json([
|
||||
'data' => new GoodsReceiptResource($goodsReceipt),
|
||||
'message' => 'Réception de marchandise mise à jour avec succès.',
|
||||
'status' => 'success'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating goods receipt: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour de la réception de marchandise.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified goods receipt.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->goodsReceiptRepository->delete((int) $id);
|
||||
if (!$deleted) {
|
||||
return response()->json(['message' => 'Réception de marchandise non trouvée ou échec de la suppression.'], 404);
|
||||
}
|
||||
return response()->json([
|
||||
'message' => 'Réception de marchandise supprimée avec succès.',
|
||||
'status' => 'success'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting goods receipt: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression de la réception de marchandise.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,713 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreInterventionRequest;
|
||||
use App\Http\Requests\StoreInterventionWithAllDataRequest;
|
||||
use App\Http\Requests\UpdateInterventionRequest;
|
||||
use App\Http\Resources\Intervention\InterventionResource;
|
||||
use App\Http\Resources\Intervention\InterventionCollection;
|
||||
use App\Repositories\InterventionRepositoryInterface;
|
||||
use App\Repositories\InterventionPractitionerRepositoryInterface;
|
||||
use App\Repositories\ClientRepositoryInterface;
|
||||
use App\Repositories\ContactRepositoryInterface;
|
||||
use App\Repositories\DeceasedRepositoryInterface;
|
||||
use App\Repositories\QuoteRepositoryInterface;
|
||||
use App\Repositories\ProductRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class InterventionController extends Controller
|
||||
{
|
||||
/**
|
||||
* @var InterventionRepositoryInterface
|
||||
*/
|
||||
protected $interventionRepository;
|
||||
|
||||
/**
|
||||
* @var InterventionPractitionerRepositoryInterface
|
||||
*/
|
||||
protected $interventionPractitionerRepository;
|
||||
|
||||
/**
|
||||
* @var ClientRepositoryInterface
|
||||
*/
|
||||
protected $clientRepository;
|
||||
|
||||
/**
|
||||
* @var ContactRepositoryInterface
|
||||
*/
|
||||
protected $contactRepository;
|
||||
|
||||
/**
|
||||
* @var DeceasedRepositoryInterface
|
||||
*/
|
||||
protected $deceasedRepository;
|
||||
|
||||
/**
|
||||
* @var QuoteRepositoryInterface
|
||||
*/
|
||||
protected $quoteRepository;
|
||||
|
||||
/**
|
||||
* @var ProductRepositoryInterface
|
||||
*/
|
||||
protected $productRepository;
|
||||
|
||||
/**
|
||||
* InterventionController constructor.
|
||||
*
|
||||
* @param InterventionRepositoryInterface $interventionRepository
|
||||
* @param InterventionPractitionerRepositoryInterface $interventionPractitionerRepository
|
||||
* @param ClientRepositoryInterface $clientRepository
|
||||
* @param ContactRepositoryInterface $contactRepository
|
||||
* @param DeceasedRepositoryInterface $deceasedRepository
|
||||
* @param QuoteRepositoryInterface $quoteRepository
|
||||
* @param ProductRepositoryInterface $productRepository
|
||||
*/
|
||||
public function __construct(
|
||||
InterventionRepositoryInterface $interventionRepository,
|
||||
InterventionPractitionerRepositoryInterface $interventionPractitionerRepository,
|
||||
ClientRepositoryInterface $clientRepository,
|
||||
ContactRepositoryInterface $contactRepository,
|
||||
DeceasedRepositoryInterface $deceasedRepository,
|
||||
QuoteRepositoryInterface $quoteRepository,
|
||||
ProductRepositoryInterface $productRepository,
|
||||
private \App\Repositories\ClientLocationRepositoryInterface $clientLocationRepository
|
||||
)
|
||||
{
|
||||
$this->interventionRepository = $interventionRepository;
|
||||
$this->interventionPractitionerRepository = $interventionPractitionerRepository;
|
||||
$this->clientRepository = $clientRepository;
|
||||
$this->contactRepository = $contactRepository;
|
||||
$this->deceasedRepository = $deceasedRepository;
|
||||
$this->quoteRepository = $quoteRepository;
|
||||
$this->productRepository = $productRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*/
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$filters = $request->only([
|
||||
'client_id',
|
||||
'deceased_id',
|
||||
'status',
|
||||
'type',
|
||||
'start_date',
|
||||
'end_date',
|
||||
'sort_by',
|
||||
'sort_order'
|
||||
]);
|
||||
|
||||
$perPage = $request->input('per_page', 15);
|
||||
|
||||
$interventions = $this->interventionRepository->getAllPaginated($filters, $perPage);
|
||||
|
||||
return response()->json(new InterventionCollection($interventions));
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
Log::error('Error fetching interventions list: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des interventions.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*/
|
||||
public function store(StoreInterventionRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$validated = $request->validated();
|
||||
|
||||
$intervention = $this->interventionRepository->create($validated);
|
||||
|
||||
return response()->json(new InterventionResource($intervention), Response::HTTP_CREATED);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
Log::error('Error creating intervention: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création de l\'intervention.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an intervention with all related data (deceased, client, contact, location, documents).
|
||||
*/
|
||||
public function createInterventionalldata(StoreInterventionWithAllDataRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$validated = $request->validated();
|
||||
|
||||
// Wrap everything in a database transaction
|
||||
$result = DB::transaction(function () use ($validated) {
|
||||
// Step 1: Handle Deceased (Create or Link)
|
||||
$deceased = null;
|
||||
if (!empty($validated['deceased_id'])) {
|
||||
$deceased = $this->deceasedRepository->findById($validated['deceased_id']);
|
||||
}
|
||||
else {
|
||||
$deceasedData = $validated['deceased'];
|
||||
$deceased = $this->deceasedRepository->create($deceasedData);
|
||||
}
|
||||
|
||||
// Step 2: Link existing client or create a new one
|
||||
if (!empty($validated['client_id'])) {
|
||||
$client = $this->clientRepository->find($validated['client_id']);
|
||||
}
|
||||
else {
|
||||
$clientData = $validated['client'];
|
||||
$client = $this->clientRepository->create($clientData);
|
||||
}
|
||||
|
||||
// Step 3: Create the contact (if provided)
|
||||
$contactId = null;
|
||||
if (!empty($validated['contact'])) {
|
||||
$contactData = array_merge($validated['contact'], [
|
||||
'client_id' => $client->id
|
||||
]);
|
||||
$contact = $this->contactRepository->create($contactData);
|
||||
$contactId = $contact->id;
|
||||
}
|
||||
|
||||
// Step 4: Handle Location
|
||||
$locationData = $validated['location'] ?? [];
|
||||
$locationId = $validated['location_id'] ?? null;
|
||||
$locationNotes = '';
|
||||
|
||||
if (!$locationId && !empty($locationData)) {
|
||||
// Create new location for the client
|
||||
$locData = array_merge($locationData, [
|
||||
'client_id' => $client->id,
|
||||
'is_default' => false
|
||||
]);
|
||||
$newLocation = $this->clientLocationRepository->create($locData);
|
||||
$locationId = $newLocation->id;
|
||||
}
|
||||
|
||||
if ($locationId) {
|
||||
// Fetch location to add details to notes if needed, or just rely on relation.
|
||||
// For now, let's keep the legacy behavior of adding text to notes for quick reference,
|
||||
// but also link the ID. Use the provided data or fetch?
|
||||
// If we have an ID, we might not have the text data in $locationData if it came from search.
|
||||
// So we only append text notes if we have $locationData (Create mode or if frontend sends it).
|
||||
}
|
||||
|
||||
if (!empty($locationData)) {
|
||||
$locationParts = [];
|
||||
if (!empty($locationData['name'])) {
|
||||
$locationParts[] = 'Lieu: ' . $locationData['name'];
|
||||
}
|
||||
if (!empty($locationData['address'])) {
|
||||
$locationParts[] = 'Adresse: ' . $locationData['address'];
|
||||
}
|
||||
if (!empty($locationData['city'])) {
|
||||
$locationParts[] = 'Ville: ' . $locationData['city'];
|
||||
}
|
||||
if (!empty($locationData['access_instructions'])) {
|
||||
$locationParts[] = 'Instructions: ' . $locationData['access_instructions'];
|
||||
}
|
||||
if (!empty($locationData['notes'])) {
|
||||
$locationParts[] = 'Notes: ' . $locationData['notes'];
|
||||
}
|
||||
$locationNotes = !empty($locationParts) ? "\n\n" . implode("\n", $locationParts) : '';
|
||||
}
|
||||
|
||||
// Step 5: Create the intervention
|
||||
$interventionData = array_merge($validated['intervention'], [
|
||||
'deceased_id' => $deceased->id,
|
||||
'client_id' => $client->id,
|
||||
'location_id' => $locationId,
|
||||
'notes' => ($validated['intervention']['notes'] ?? '') . $locationNotes
|
||||
]);
|
||||
$intervention = $this->interventionRepository->create($interventionData);
|
||||
|
||||
// Step 5a: Assign practitioners if provided
|
||||
if (!empty($validated['intervention']['principal_practitioner_id'])) {
|
||||
$this->interventionPractitionerRepository->createAssignment(
|
||||
$intervention->id,
|
||||
(int)$validated['intervention']['principal_practitioner_id'],
|
||||
'principal'
|
||||
);
|
||||
}
|
||||
|
||||
if (!empty($validated['intervention']['assistant_practitioner_ids']) && is_array($validated['intervention']['assistant_practitioner_ids'])) {
|
||||
foreach ($validated['intervention']['assistant_practitioner_ids'] as $assistantId) {
|
||||
$this->interventionPractitionerRepository->createAssignment(
|
||||
$intervention->id,
|
||||
(int)$assistantId,
|
||||
'assistant'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
empty($validated['intervention']['principal_practitioner_id']) &&
|
||||
!empty($validated['intervention']['practitioners']) &&
|
||||
is_array($validated['intervention']['practitioners'])
|
||||
) {
|
||||
foreach ($validated['intervention']['practitioners'] as $index => $practitionerId) {
|
||||
$role = $index === 0 ? 'principal' : 'assistant';
|
||||
$this->interventionPractitionerRepository->createAssignment(
|
||||
$intervention->id,
|
||||
(int)$practitionerId,
|
||||
$role
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$intervention->load('practitioners');
|
||||
|
||||
// Step 5b: Create a Quote for this intervention
|
||||
try {
|
||||
$interventionProduct = $this->productRepository->findInterventionProduct();
|
||||
|
||||
if ($interventionProduct) {
|
||||
// Calculate totals
|
||||
$quantity = 1;
|
||||
// Ideally fetch TVA rate from product, default to 20% if not set
|
||||
// Assuming product has tva_rate relationship or simple logic
|
||||
$tvaRateValue = 20;
|
||||
|
||||
$unitPrice = $interventionProduct->prix_unitaire;
|
||||
$totalHt = $unitPrice * $quantity;
|
||||
$totalTva = $totalHt * ($tvaRateValue / 100);
|
||||
$totalTtc = $totalHt + $totalTva;
|
||||
|
||||
$quoteData = [
|
||||
'client_id' => $client->id,
|
||||
'status' => 'brouillon',
|
||||
'quote_date' => now()->toDateString(),
|
||||
'currency' => 'EUR',
|
||||
'valid_until' => now()->addDays(30)->toDateString(),
|
||||
'total_ht' => $totalHt,
|
||||
'total_tva' => $totalTva,
|
||||
'total_ttc' => $totalTtc,
|
||||
'lines' => [
|
||||
[
|
||||
'product_id' => $interventionProduct->id,
|
||||
'description' => 'Intervention: ' . ($intervention->type ?? 'Standard'),
|
||||
'units_qty' => $quantity,
|
||||
'unit_price' => $unitPrice,
|
||||
'discount_pct' => 0,
|
||||
'total_ht' => $totalHt,
|
||||
// 'tva_rate_id' => ... if needed
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
$quote = $this->quoteRepository->create($quoteData);
|
||||
|
||||
// Update the intervention with the newly created quote ID
|
||||
$intervention->update(['quote_id' => $quote->id]);
|
||||
|
||||
Log::info('Quote auto-created for intervention', ['intervention_id' => $intervention->id, 'quote_id' => $quote->id]);
|
||||
}
|
||||
else {
|
||||
Log::warning('No intervention product found, skipping auto-quote creation', ['intervention_id' => $intervention->id]);
|
||||
}
|
||||
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
Log::error('Failed to auto-create quote for intervention: ' . $e->getMessage());
|
||||
// Silently fail for the quote part to not block intervention creation
|
||||
}
|
||||
|
||||
|
||||
// Step 6: Handle document uploads (if any)
|
||||
$documents = $validated['documents'] ?? [];
|
||||
if (!empty($documents)) {
|
||||
foreach ($documents as $documentData) {
|
||||
if (isset($documentData['file']) && $documentData['file']->isValid()) {
|
||||
// Store the file and create intervention attachment
|
||||
// This is a placeholder - implement actual file upload logic
|
||||
// $path = $documentData['file']->store('intervention_documents');
|
||||
// Create intervention attachment record
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return all created data
|
||||
return [
|
||||
'intervention' => $intervention,
|
||||
'deceased' => $deceased,
|
||||
'client' => $client,
|
||||
'contact_id' => $contactId,
|
||||
'documents_count' => count($documents)
|
||||
];
|
||||
});
|
||||
|
||||
Log::info('Intervention with all data created successfully', [
|
||||
'intervention_id' => $result['intervention']->id,
|
||||
'deceased_id' => $result['deceased']->id,
|
||||
'client_id' => $result['client']->id,
|
||||
'documents_count' => $result['documents_count']
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Intervention créée avec succès',
|
||||
'data' => [
|
||||
'intervention' => new InterventionResource($result['intervention']),
|
||||
'deceased' => $result['deceased'],
|
||||
'client' => $result['client'],
|
||||
'contact_id' => $result['contact_id'],
|
||||
'documents_count' => $result['documents_count']
|
||||
]
|
||||
], Response::HTTP_CREATED);
|
||||
|
||||
}
|
||||
catch (\Illuminate\Validation\ValidationException $e) {
|
||||
// Validation errors are handled by the FormRequest
|
||||
return response()->json([
|
||||
'message' => 'Données invalides',
|
||||
'errors' => $e->errors()
|
||||
], Response::HTTP_UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
Log::error('Error creating intervention with all data: ' . $e->getMessage(), [
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'input' => $request->except(['documents']) // Don't log file data
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création de l\'intervention.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*/
|
||||
public function show(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$intervention = $this->interventionRepository->findById($id);
|
||||
|
||||
return response()->json(new InterventionResource($intervention));
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
Log::error('Error fetching intervention details: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Intervention non trouvée ou une erreur est survenue.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*/
|
||||
public function update(UpdateInterventionRequest $request, int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$intervention = $this->interventionRepository->findById($id);
|
||||
|
||||
$validated = $request->validated();
|
||||
|
||||
$updatedIntervention = $this->interventionRepository->update($intervention, $validated);
|
||||
|
||||
return response()->json(new InterventionResource($updatedIntervention));
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
Log::error('Error updating intervention: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour de l\'intervention.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*/
|
||||
public function destroy(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$intervention = $this->interventionRepository->findById($id);
|
||||
|
||||
$this->interventionRepository->delete($intervention);
|
||||
|
||||
return response()->json(null, Response::HTTP_NO_CONTENT);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
Log::error('Error deleting intervention: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression de l\'intervention.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the status of an intervention.
|
||||
*/
|
||||
public function changeStatus(Request $request, int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'status' => 'required|in:demande,planifie,en_cours,termine,annule'
|
||||
]);
|
||||
|
||||
$intervention = $this->interventionRepository->findById($id);
|
||||
|
||||
$updatedIntervention = $this->interventionRepository->changeStatus(
|
||||
$intervention,
|
||||
$validated['status']
|
||||
);
|
||||
|
||||
return response()->json(new InterventionResource($updatedIntervention));
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
Log::error('Error changing intervention status: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la modification du statut de l\'intervention.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get interventions for a specific month.
|
||||
*/
|
||||
public function byMonth(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'year' => 'required|integer|min:2000|max:2100',
|
||||
'month' => 'required|integer|min:1|max:12'
|
||||
]);
|
||||
|
||||
$interventions = $this->interventionRepository->getByMonth(
|
||||
$validated['year'],
|
||||
$validated['month']
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'data' => $interventions->map(function ($intervention) {
|
||||
return new InterventionResource($intervention);
|
||||
}),
|
||||
'meta' => [
|
||||
'total' => $interventions->count(),
|
||||
'year' => $validated['year'],
|
||||
'month' => $validated['month'],
|
||||
]
|
||||
]);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
Log::error('Error fetching interventions by month: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des interventions du mois.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create assignment of practitioners to an intervention
|
||||
*
|
||||
* @param Request $request
|
||||
* @param int $id
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function createAssignment(Request $request, int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'principal_practitioner_id' => 'nullable|integer|exists:thanatopractitioners,id',
|
||||
'assistant_practitioner_ids' => 'nullable|array',
|
||||
'assistant_practitioner_ids.*' => 'integer|exists:thanatopractitioners,id',
|
||||
]);
|
||||
|
||||
|
||||
$intervention = $this->interventionRepository->findById($id);
|
||||
|
||||
if (!$intervention) {
|
||||
return response()->json([
|
||||
'message' => 'Intervention non trouvée.'
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Remove existing principal practitioner first
|
||||
if (isset($validated['principal_practitioner_id'])) {
|
||||
$principalId = $validated['principal_practitioner_id'];
|
||||
$this->interventionPractitionerRepository->createAssignment($id, $principalId, 'principal');
|
||||
}
|
||||
|
||||
// Handle assistant practitioners
|
||||
if (isset($validated['assistant_practitioner_ids']) && is_array($validated['assistant_practitioner_ids'])) {
|
||||
foreach ($validated['assistant_practitioner_ids'] as $assistantId) {
|
||||
$this->interventionPractitionerRepository->createAssignment($id, $assistantId, 'assistant');
|
||||
}
|
||||
}
|
||||
|
||||
// Load the intervention with practitioners to return updated data
|
||||
$intervention->load('practitioners');
|
||||
$practitioners = $intervention->practitioners;
|
||||
|
||||
return response()->json([
|
||||
'data' => new InterventionResource($intervention),
|
||||
'message' => 'Assignment(s) créé(s) avec succès.',
|
||||
'practitioners_count' => $practitioners->count(),
|
||||
'practitioners' => $practitioners->map(function ($p) {
|
||||
return [
|
||||
'id' => $p->id,
|
||||
'full_name' => $p->full_name ?? ($p->first_name . ' ' . $p->last_name),
|
||||
'role' => $p->pivot->role ?? 'unknown'
|
||||
];
|
||||
})->toArray()
|
||||
], Response::HTTP_OK);
|
||||
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création de l\'assignment.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unassign a practitioner from an intervention
|
||||
*
|
||||
* @param Request $request
|
||||
* @param int $interventionId
|
||||
* @param int $practitionerId
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function unassignPractitioner(Request $request, int $interventionId, int $practitionerId): JsonResponse
|
||||
{
|
||||
try {
|
||||
Log::info('Unassigning practitioner from intervention', [
|
||||
'intervention_id' => $interventionId,
|
||||
'practitioner_id' => $practitionerId
|
||||
]);
|
||||
|
||||
// Validate that the intervention exists
|
||||
$intervention = $this->interventionRepository->findById($interventionId);
|
||||
|
||||
if (!$intervention) {
|
||||
return response()->json([
|
||||
'message' => 'Intervention non trouvée.'
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Check if the practitioner is actually assigned to this intervention
|
||||
$isAssigned = $this->interventionPractitionerRepository->isPractitionerAssigned($interventionId, $practitionerId);
|
||||
|
||||
if (!$isAssigned) {
|
||||
return response()->json([
|
||||
'message' => 'Le praticien n\'est pas assigné à cette intervention.'
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Remove the practitioner assignment
|
||||
$deleted = $this->interventionPractitionerRepository->removeAssignment($interventionId, $practitionerId);
|
||||
|
||||
if ($deleted > 0) {
|
||||
Log::info('Practitioner unassigned successfully', [
|
||||
'intervention_id' => $interventionId,
|
||||
'practitioner_id' => $practitionerId,
|
||||
'deleted_records' => $deleted
|
||||
]);
|
||||
|
||||
// Reload intervention with remaining practitioners
|
||||
$intervention->load('practitioners');
|
||||
$remainingPractitioners = $intervention->practitioners;
|
||||
|
||||
return response()->json([
|
||||
'data' => new InterventionResource($intervention),
|
||||
'message' => 'Praticien désassigné avec succès.',
|
||||
'remaining_practitioners_count' => $remainingPractitioners->count(),
|
||||
'remaining_practitioners' => $remainingPractitioners->map(function ($p) {
|
||||
return [
|
||||
'id' => $p->id,
|
||||
'employee_name' => $p->employee->full_name ?? ($p->employee->first_name . ' ' . $p->employee->last_name),
|
||||
'role' => $p->pivot->role ?? 'unknown'
|
||||
];
|
||||
})->toArray()
|
||||
], Response::HTTP_OK);
|
||||
}
|
||||
else {
|
||||
Log::warning('No practitioner assignment found to delete', [
|
||||
'intervention_id' => $interventionId,
|
||||
'practitioner_id' => $practitionerId
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Aucun assignment de praticien trouvé à supprimer.'
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
Log::error('Error unassigning practitioner from intervention: ' . $e->getMessage(), [
|
||||
'intervention_id' => $interventionId,
|
||||
'practitioner_id' => $practitionerId,
|
||||
'request_data' => $request->all(),
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la désassignation du praticien.',
|
||||
'error' => $e->getMessage()
|
||||
], Response::HTTP_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug endpoint to check practitioners in database
|
||||
*/
|
||||
public function debugPractitioners(int $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$intervention = $this->interventionRepository->findById($id);
|
||||
|
||||
// Direct database query
|
||||
$dbPractitioners = DB::table('intervention_practitioner')
|
||||
->where('intervention_id', $id)
|
||||
->get();
|
||||
|
||||
// Eager loaded practitioners
|
||||
$eagerPractitioners = $intervention->practitioners()->get();
|
||||
|
||||
return response()->json([
|
||||
'intervention_id' => $id,
|
||||
'database_records' => $dbPractitioners,
|
||||
'eager_loaded_count' => $eagerPractitioners->count(),
|
||||
'eager_loaded_data' => $eagerPractitioners->map(function ($p) {
|
||||
return [
|
||||
'id' => $p->id,
|
||||
'full_name' => $p->full_name ?? ($p->first_name . ' ' . $p->last_name),
|
||||
'role' => $p->pivot->role ?? 'unknown'
|
||||
];
|
||||
})->toArray()
|
||||
]);
|
||||
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
Log::error('Error in debug practitioners: ' . $e->getMessage());
|
||||
return response()->json(['error' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
222
thanasoft-back/app/Http/Controllers/Api/InvoiceController.php
Normal file
222
thanasoft-back/app/Http/Controllers/Api/InvoiceController.php
Normal file
@ -0,0 +1,222 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreInvoiceRequest;
|
||||
use App\Http\Requests\UpdateInvoiceRequest;
|
||||
use App\Http\Resources\InvoiceResource;
|
||||
use App\Repositories\InvoiceRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use App\Mail\DocumentMail;
|
||||
|
||||
class InvoiceController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected InvoiceRepositoryInterface $invoiceRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of invoices.
|
||||
*/
|
||||
public function index(): AnonymousResourceCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$invoices = $this->invoiceRepository->all();
|
||||
return InvoiceResource::collection($invoices);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching invoices: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des factures.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created invoice.
|
||||
*/
|
||||
public function store(StoreInvoiceRequest $request): InvoiceResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$invoice = $this->invoiceRepository->create($request->validated());
|
||||
return new InvoiceResource($invoice);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating invoice: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création de la facture.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified invoice.
|
||||
*/
|
||||
public function show(string $id): InvoiceResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$invoice = $this->invoiceRepository->find($id);
|
||||
|
||||
if (! $invoice) {
|
||||
return response()->json([
|
||||
'message' => 'Facture non trouvée.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new InvoiceResource($invoice);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching invoice: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'invoice_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération de la facture.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified invoice.
|
||||
*/
|
||||
public function update(UpdateInvoiceRequest $request, string $id): InvoiceResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->invoiceRepository->update($id, $request->validated());
|
||||
|
||||
if (! $updated) {
|
||||
return response()->json([
|
||||
'message' => 'Facture non trouvée ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$invoice = $this->invoiceRepository->find($id);
|
||||
return new InvoiceResource($invoice);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating invoice: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'invoice_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour de la facture.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified invoice.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->invoiceRepository->delete($id);
|
||||
|
||||
if (! $deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Facture non trouvée ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Facture supprimée avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting invoice: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'invoice_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression de la facture.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an invoice from a quote.
|
||||
*/
|
||||
public function createFromQuote(string $quoteId): InvoiceResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$invoice = $this->invoiceRepository->createFromQuote($quoteId);
|
||||
return new InvoiceResource($invoice);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating invoice from quote: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'quote_id' => $quoteId,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création de la facture depuis le devis.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the invoice by email to the client.
|
||||
*/
|
||||
public function sendByEmail(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$invoice = $this->invoiceRepository->find($id);
|
||||
|
||||
if (!$invoice) {
|
||||
return response()->json(['message' => 'Facture non trouvée.'], 404);
|
||||
}
|
||||
|
||||
if (!$invoice->client || !$invoice->client->email) {
|
||||
return response()->json(['message' => 'Le client n\'a pas d\'adresse email.'], 422);
|
||||
}
|
||||
|
||||
// Load lines to ensure they are available in the view
|
||||
$invoice->load('lines');
|
||||
|
||||
// Generate PDF
|
||||
$pdfContent = Pdf::loadView('pdf.invoice_pdf', ['invoice' => $invoice])->output();
|
||||
|
||||
// Send Email
|
||||
Mail::to($invoice->client->email)->send(new DocumentMail($invoice, 'invoice', $pdfContent));
|
||||
|
||||
return response()->json([
|
||||
'message' => 'La facture a été envoyée avec succès à ' . $invoice->client->email,
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error sending invoice email: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'invoice_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de l\'envoi de l\'email.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,320 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StorePractitionerDocumentRequest;
|
||||
use App\Http\Requests\UpdatePractitionerDocumentRequest;
|
||||
use App\Http\Resources\Employee\PractitionerDocumentResource;
|
||||
use App\Http\Resources\Employee\PractitionerDocumentCollection;
|
||||
use App\Repositories\PractitionerDocumentRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class PractitionerDocumentController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PractitionerDocumentRepositoryInterface $practitionerDocumentRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of practitioner documents.
|
||||
*/
|
||||
public function index(Request $request): PractitionerDocumentCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$filters = [
|
||||
'search' => $request->get('search'),
|
||||
'practitioner_id' => $request->get('practitioner_id'),
|
||||
'doc_type' => $request->get('doc_type'),
|
||||
'valid_only' => $request->get('valid_only'),
|
||||
'sort_by' => $request->get('sort_by', 'created_at'),
|
||||
'sort_direction' => $request->get('sort_direction', 'desc'),
|
||||
];
|
||||
|
||||
// Remove null filters
|
||||
$filters = array_filter($filters, function ($value) {
|
||||
return $value !== null && $value !== '';
|
||||
});
|
||||
|
||||
$documents = $this->practitionerDocumentRepository->getAll($filters);
|
||||
|
||||
return new PractitionerDocumentCollection($documents);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching practitioner documents: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des documents des praticiens.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display paginated practitioner documents.
|
||||
*/
|
||||
public function paginated(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = $request->get('per_page', 15);
|
||||
$result = $this->practitionerDocumentRepository->getPaginated($perPage);
|
||||
|
||||
return response()->json([
|
||||
'data' => new PractitionerDocumentCollection($result['documents']),
|
||||
'pagination' => $result['pagination'],
|
||||
'message' => 'Documents des praticiens récupérés avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching paginated practitioner documents: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des documents des praticiens.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get documents by practitioner ID.
|
||||
*/
|
||||
public function byPractitioner(string $practitionerId): PractitionerDocumentCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$documents = $this->practitionerDocumentRepository->getByPractitionerId((int) $practitionerId);
|
||||
return new PractitionerDocumentCollection($documents);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching documents by practitioner: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'practitioner_id' => $practitionerId,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des documents du praticien.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get documents by type.
|
||||
*/
|
||||
public function byType(Request $request): PractitionerDocumentCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$docType = $request->get('doc_type');
|
||||
|
||||
if (!$docType) {
|
||||
return response()->json([
|
||||
'message' => 'Le paramètre doc_type est requis.',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$documents = $this->practitionerDocumentRepository->getByDocumentType($docType);
|
||||
return new PractitionerDocumentCollection($documents);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching documents by type: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'doc_type' => $docType,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des documents par type.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get valid documents (not expired).
|
||||
*/
|
||||
public function valid(): PractitionerDocumentCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$documents = $this->practitionerDocumentRepository->getValid();
|
||||
return new PractitionerDocumentCollection($documents);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching valid documents: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des documents valides.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get expired documents.
|
||||
*/
|
||||
public function expired(): PractitionerDocumentCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$documents = $this->practitionerDocumentRepository->getExpired();
|
||||
return new PractitionerDocumentCollection($documents);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching expired documents: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des documents expirés.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get practitioner document statistics.
|
||||
*/
|
||||
public function statistics(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$statistics = $this->practitionerDocumentRepository->getStatistics();
|
||||
|
||||
return response()->json([
|
||||
'data' => $statistics,
|
||||
'message' => 'Statistiques des documents des praticiens récupérées avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching practitioner document statistics: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des statistiques.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created practitioner document.
|
||||
*/
|
||||
public function store(StorePractitionerDocumentRequest $request): PractitionerDocumentResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$document = $this->practitionerDocumentRepository->create($request->validated());
|
||||
return new PractitionerDocumentResource($document);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating practitioner document: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création du document du praticien.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified practitioner document.
|
||||
*/
|
||||
public function show(string $id): PractitionerDocumentResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$document = $this->practitionerDocumentRepository->find($id);
|
||||
|
||||
if (!$document) {
|
||||
return response()->json([
|
||||
'message' => 'Document du praticien non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new PractitionerDocumentResource($document);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching practitioner document: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'document_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du document du praticien.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified practitioner document.
|
||||
*/
|
||||
public function update(UpdatePractitionerDocumentRequest $request, string $id): PractitionerDocumentResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->practitionerDocumentRepository->update($id, $request->validated());
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Document du praticien non trouvé ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$document = $this->practitionerDocumentRepository->find($id);
|
||||
return new PractitionerDocumentResource($document);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating practitioner document: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'document_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du document du praticien.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified practitioner document.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->practitionerDocumentRepository->delete($id);
|
||||
|
||||
if (!$deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Document du praticien non trouvé ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Document du praticien supprimé avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting practitioner document: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'document_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression du document du praticien.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
154
thanasoft-back/app/Http/Controllers/Api/PriceListController.php
Normal file
154
thanasoft-back/app/Http/Controllers/Api/PriceListController.php
Normal file
@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StorePriceListRequest;
|
||||
use App\Http\Requests\UpdatePriceListRequest;
|
||||
use App\Http\Resources\PriceListResource;
|
||||
use App\Repositories\PriceListRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class PriceListController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly PriceListRepositoryInterface $priceListRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of price lists.
|
||||
*/
|
||||
public function index(): AnonymousResourceCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$priceLists = $this->priceListRepository->all()->sortBy('name')->values();
|
||||
|
||||
return PriceListResource::collection($priceLists);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching price lists: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des listes de prix.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created price list.
|
||||
*/
|
||||
public function store(StorePriceListRequest $request): PriceListResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$priceList = $this->priceListRepository->create($request->validated());
|
||||
|
||||
return new PriceListResource($priceList);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating price list: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création de la liste de prix.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified price list.
|
||||
*/
|
||||
public function show(string $id): PriceListResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$priceList = $this->priceListRepository->find($id);
|
||||
|
||||
if (! $priceList) {
|
||||
return response()->json([
|
||||
'message' => 'Liste de prix non trouvée.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new PriceListResource($priceList);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching price list: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'price_list_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération de la liste de prix.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified price list.
|
||||
*/
|
||||
public function update(UpdatePriceListRequest $request, string $id): PriceListResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->priceListRepository->update($id, $request->validated());
|
||||
|
||||
if (! $updated) {
|
||||
return response()->json([
|
||||
'message' => 'Liste de prix non trouvée ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$priceList = $this->priceListRepository->find($id);
|
||||
|
||||
return new PriceListResource($priceList);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating price list: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'price_list_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour de la liste de prix.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified price list.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->priceListRepository->delete($id);
|
||||
|
||||
if (! $deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Liste de prix non trouvée ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Liste de prix supprimée avec succès.',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting price list: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'price_list_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression de la liste de prix.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,349 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreProductCategoryRequest;
|
||||
use App\Http\Requests\UpdateProductCategoryRequest;
|
||||
use App\Http\Resources\ProductCategory\ProductCategoryResource;
|
||||
use App\Http\Resources\ProductCategory\ProductCategoryCollection;
|
||||
use App\Repositories\ProductCategoryRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ProductCategoryController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProductCategoryRepositoryInterface $productCategoryRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of product categories.
|
||||
*/
|
||||
public function index(Request $request): ProductCategoryCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = (int) $request->get('per_page', 15);
|
||||
$filters = [
|
||||
'search' => $request->get('search'),
|
||||
'active' => $request->get('active'),
|
||||
'parent_id' => $request->get('parent_id'),
|
||||
'sort_by' => $request->get('sort_by', 'name'),
|
||||
'sort_direction' => $request->get('sort_direction', 'asc'),
|
||||
];
|
||||
|
||||
// Remove null filters
|
||||
$filters = array_filter($filters, function ($value) {
|
||||
return $value !== null && $value !== '';
|
||||
});
|
||||
|
||||
$categories = $this->productCategoryRepository->paginate($perPage, $filters);
|
||||
|
||||
return new ProductCategoryCollection($categories);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching product categories: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des catégories de produits.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created product category.
|
||||
*/
|
||||
public function store(StoreProductCategoryRequest $request): ProductCategoryResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$category = $this->productCategoryRepository->create($request->validated());
|
||||
return new ProductCategoryResource($category);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating product category: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création de la catégorie.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified product category.
|
||||
*/
|
||||
public function show(string $id): ProductCategoryResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$category = $this->productCategoryRepository->find($id);
|
||||
|
||||
if (!$category) {
|
||||
return response()->json([
|
||||
'message' => 'Catégorie non trouvée.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new ProductCategoryResource($category);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching product category: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'category_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération de la catégorie.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified product category.
|
||||
*/
|
||||
public function update(UpdateProductCategoryRequest $request, string $id): ProductCategoryResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->productCategoryRepository->update($id, $request->validated());
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Catégorie non trouvée ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$category = $this->productCategoryRepository->find($id);
|
||||
return new ProductCategoryResource($category);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating product category: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'category_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour de la catégorie.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified product category.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
// Check if category can be deleted
|
||||
if (!$this->productCategoryRepository->canDelete($id)) {
|
||||
return response()->json([
|
||||
'message' => 'Impossible de supprimer cette catégorie. Elle peut avoir des sous-catégories ou des produits associés.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$deleted = $this->productCategoryRepository->delete($id);
|
||||
|
||||
if (!$deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Catégorie non trouvée ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Catégorie supprimée avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting product category: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'category_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression de la catégorie.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active categories only
|
||||
*/
|
||||
public function active(): ProductCategoryCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$categories = $this->productCategoryRepository->getActive();
|
||||
return new ProductCategoryCollection(collect(['data' => $categories]));
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching active product categories: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des catégories actives.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get root categories (no parent)
|
||||
*/
|
||||
public function roots(): ProductCategoryCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$categories = $this->productCategoryRepository->getRoots();
|
||||
return new ProductCategoryCollection(collect(['data' => $categories]));
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching root product categories: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des catégories racine.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get categories with their children (hierarchical structure)
|
||||
*/
|
||||
public function hierarchical(): ProductCategoryCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$categories = $this->productCategoryRepository->getWithChildren();
|
||||
return new ProductCategoryCollection(collect(['data' => $categories]));
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching hierarchical product categories: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération de la structure hiérarchique.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search categories by name, code or description
|
||||
*/
|
||||
public function search(Request $request): ProductCategoryCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$term = $request->get('term', '');
|
||||
$perPage = (int) $request->get('per_page', 15);
|
||||
|
||||
if (empty($term)) {
|
||||
return response()->json([
|
||||
'message' => 'Le paramètre "term" est requis pour la recherche.',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$categories = $this->productCategoryRepository->search($term, $perPage);
|
||||
|
||||
return new ProductCategoryCollection($categories);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error searching product categories: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'search_term' => $term,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la recherche des catégories.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get category statistics
|
||||
*/
|
||||
public function statistics(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$stats = $this->productCategoryRepository->getStatistics();
|
||||
|
||||
return response()->json([
|
||||
'data' => $stats,
|
||||
'message' => 'Statistiques des catégories récupérées avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching product category statistics: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des statistiques.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle category active status
|
||||
*/
|
||||
public function toggleActive(Request $request, string $id): ProductCategoryResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'active' => 'required|boolean',
|
||||
]);
|
||||
|
||||
$category = $this->productCategoryRepository->find($id);
|
||||
|
||||
if (!$category) {
|
||||
return response()->json([
|
||||
'message' => 'Catégorie non trouvée.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$updated = $this->productCategoryRepository->update($id, [
|
||||
'active' => $request->boolean('active')
|
||||
]);
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Échec de la mise à jour du statut.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$category = $this->productCategoryRepository->find($id);
|
||||
return new ProductCategoryResource($category);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error toggling product category status: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'category_id' => $id,
|
||||
'data' => $request->all(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du statut.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
376
thanasoft-back/app/Http/Controllers/Api/ProductController.php
Normal file
376
thanasoft-back/app/Http/Controllers/Api/ProductController.php
Normal file
@ -0,0 +1,376 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreProductRequest;
|
||||
use App\Http\Requests\UpdateProductRequest;
|
||||
use App\Http\Resources\Product\ProductResource;
|
||||
use App\Http\Resources\Product\ProductCollection;
|
||||
use App\Repositories\ProductRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ProductController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ProductRepositoryInterface $productRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of products.
|
||||
*/
|
||||
public function index(Request $request): ProductCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = (int) $request->get('per_page', 15);
|
||||
$filters = [
|
||||
'search' => $request->get('search'),
|
||||
'categorie' => $request->get('categorie_id'),
|
||||
'fournisseur_id' => $request->get('fournisseur_id'),
|
||||
'low_stock' => $request->get('low_stock'),
|
||||
'expiring_soon' => $request->get('expiring_soon'),
|
||||
'is_intervention' => $request->get('is_intervention'),
|
||||
'sort_by' => $request->get('sort_by', 'created_at'),
|
||||
'sort_direction' => $request->get('sort_direction', 'desc'),
|
||||
];
|
||||
|
||||
// Remove null filters
|
||||
$filters = array_filter($filters, function ($value) {
|
||||
return $value !== null && $value !== '';
|
||||
});
|
||||
|
||||
$products = $this->productRepository->paginate($perPage, $filters);
|
||||
|
||||
return new ProductCollection($products);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching products: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des produits.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created product.
|
||||
*/
|
||||
public function store(StoreProductRequest $request): ProductResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$validatedData = $request->validated();
|
||||
|
||||
// Handle image upload
|
||||
if ($request->hasFile('image')) {
|
||||
// Create product without image first
|
||||
$product = $this->productRepository->create($validatedData);
|
||||
|
||||
// Upload and attach image
|
||||
$imagePath = $product->uploadImage($request->file('image'));
|
||||
|
||||
// Refresh product to get updated data
|
||||
$product = $this->productRepository->find($product->id);
|
||||
} else {
|
||||
$product = $this->productRepository->create($validatedData);
|
||||
}
|
||||
|
||||
return new ProductResource($product);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating product: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création du produit.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified product.
|
||||
*/
|
||||
public function show(string $id): ProductResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$product = $this->productRepository->find($id);
|
||||
|
||||
if (!$product) {
|
||||
return response()->json([
|
||||
'message' => 'Produit non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new ProductResource($product);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching product: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'product_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du produit.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search products by name.
|
||||
*/
|
||||
public function searchBy(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$name = $request->get('name', '');
|
||||
$exact = $request->boolean('exact', false);
|
||||
|
||||
if (empty($name)) {
|
||||
return response()->json([
|
||||
'message' => 'Le paramètre "name" est requis.',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$products = $this->productRepository->searchByName($name, 15, $exact);
|
||||
|
||||
return response()->json([
|
||||
'data' => $products,
|
||||
'count' => $products->count(),
|
||||
'message' => $products->count() > 0
|
||||
? 'Produits trouvés avec succès.'
|
||||
: 'Aucun produit trouvé.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error searching products by name: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'search_term' => $name,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la recherche des produits.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get products with low stock.
|
||||
*/
|
||||
public function lowStock(Request $request): ProductCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = (int) $request->get('per_page', 15);
|
||||
$products = $this->productRepository->getLowStockProducts($perPage);
|
||||
|
||||
return new ProductCollection($products);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching low stock products: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des produits à stock faible.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get products by category.
|
||||
*/
|
||||
public function byCategory(Request $request): ProductCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$categoryId = $request->get('category_id');
|
||||
$perPage = (int) $request->get('per_page', 15);
|
||||
|
||||
if (empty($categoryId)) {
|
||||
return response()->json([
|
||||
'message' => 'Le paramètre "category_id" est requis.',
|
||||
], 400);
|
||||
}
|
||||
|
||||
$products = $this->productRepository->getByCategory($categoryId, $perPage);
|
||||
|
||||
return new ProductCollection($products);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching products by category: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'category_id' => $categoryId,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des produits par catégorie.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get products statistics.
|
||||
*/
|
||||
public function statistics(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$stats = $this->productRepository->getStatistics();
|
||||
|
||||
return response()->json([
|
||||
'data' => $stats,
|
||||
'message' => 'Statistiques des produits récupérées avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching product statistics: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des statistiques.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified product.
|
||||
*/
|
||||
public function update(UpdateProductRequest $request, string $id): ProductResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$validatedData = $request->validated();
|
||||
$product = $this->productRepository->find($id);
|
||||
|
||||
if (!$product) {
|
||||
return response()->json([
|
||||
'message' => 'Produit non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
// Handle image upload/removal
|
||||
if ($request->boolean('remove_image')) {
|
||||
// Remove existing image
|
||||
$product->deleteImage();
|
||||
} elseif ($request->hasFile('image')) {
|
||||
// Upload new image
|
||||
$product->uploadImage($request->file('image'));
|
||||
}
|
||||
|
||||
// Remove image-related fields from validated data before updating other fields
|
||||
unset($validatedData['image'], $validatedData['remove_image']);
|
||||
|
||||
// Update other product fields
|
||||
$updated = $this->productRepository->update($id, $validatedData);
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Produit non trouvé ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$product = $this->productRepository->find($id);
|
||||
return new ProductResource($product);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating product: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'product_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du produit.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified product.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->productRepository->delete($id);
|
||||
|
||||
if (!$deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Produit non trouvé ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Produit supprimé avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting product: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'product_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression du produit.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update stock quantity for a product.
|
||||
*/
|
||||
public function updateStock(Request $request, string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$request->validate([
|
||||
'stock_actuel' => 'required|numeric|min:0',
|
||||
]);
|
||||
|
||||
$updated = $this->productRepository->updateStock((int) $id, $request->stock_actuel);
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Produit non trouvé ou échec de la mise à jour du stock.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$product = $this->productRepository->find($id);
|
||||
|
||||
return response()->json([
|
||||
'data' => new ProductResource($product),
|
||||
'message' => 'Stock mis à jour avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating product stock: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'product_id' => $id,
|
||||
'stock_data' => $request->all(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du stock.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,226 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StorePurchaseOrderRequest;
|
||||
use App\Http\Requests\UpdatePurchaseOrderRequest;
|
||||
use App\Http\Resources\Fournisseur\PurchaseOrderResource;
|
||||
use App\Models\GoodsReceipt;
|
||||
use App\Models\Warehouse;
|
||||
use App\Repositories\GoodsReceiptRepositoryInterface;
|
||||
use App\Repositories\PurchaseOrderRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class PurchaseOrderController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected PurchaseOrderRepositoryInterface $purchaseOrderRepository,
|
||||
protected GoodsReceiptRepositoryInterface $goodsReceiptRepository
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of purchase orders.
|
||||
*/
|
||||
public function index(): AnonymousResourceCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$purchaseOrders = $this->purchaseOrderRepository->all();
|
||||
return PurchaseOrderResource::collection($purchaseOrders);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
Log::error('Error fetching purchase orders: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des commandes fournisseurs.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created purchase order.
|
||||
*/
|
||||
public function store(StorePurchaseOrderRequest $request): PurchaseOrderResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$purchaseOrder = $this->purchaseOrderRepository->create($request->validated());
|
||||
|
||||
// If PO is created directly as validated/delivered, ensure a draft goods receipt exists.
|
||||
if ($purchaseOrder && in_array($purchaseOrder->status, ['confirmee', 'livree'], true)) {
|
||||
$this->createGoodsReceiptFromValidatedPurchaseOrder($purchaseOrder);
|
||||
}
|
||||
|
||||
return new PurchaseOrderResource($purchaseOrder);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
Log::error('Error creating purchase order: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création de la commande fournisseur.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified purchase order.
|
||||
*/
|
||||
public function show(string $id): PurchaseOrderResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$purchaseOrder = $this->purchaseOrderRepository->find($id);
|
||||
|
||||
if (!$purchaseOrder) {
|
||||
return response()->json([
|
||||
'message' => 'Commande fournisseur non trouvée.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new PurchaseOrderResource($purchaseOrder);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
Log::error('Error fetching purchase order: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'purchase_order_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération de la commande fournisseur.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified purchase order.
|
||||
*/
|
||||
public function update(UpdatePurchaseOrderRequest $request, string $id): PurchaseOrderResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->purchaseOrderRepository->update($id, $request->validated());
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Commande fournisseur non trouvée ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$purchaseOrder = $this->purchaseOrderRepository->find($id);
|
||||
|
||||
// Ensure draft goods receipt exists when PO is validated/delivered.
|
||||
// Idempotent: guarded by purchase_order_id existence check in helper.
|
||||
if ($purchaseOrder && in_array($purchaseOrder->status, ['confirmee', 'livree'], true)) {
|
||||
$this->createGoodsReceiptFromValidatedPurchaseOrder($purchaseOrder);
|
||||
}
|
||||
|
||||
return new PurchaseOrderResource($purchaseOrder);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
Log::error('Error updating purchase order: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'purchase_order_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour de la commande fournisseur.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a draft goods receipt when a purchase order is validated.
|
||||
*/
|
||||
protected function createGoodsReceiptFromValidatedPurchaseOrder($purchaseOrder): void
|
||||
{
|
||||
$alreadyExists = GoodsReceipt::query()
|
||||
->where('purchase_order_id', $purchaseOrder->id)
|
||||
->exists();
|
||||
|
||||
if ($alreadyExists) {
|
||||
return;
|
||||
}
|
||||
|
||||
$warehouseId = Warehouse::query()->value('id');
|
||||
if (!$warehouseId) {
|
||||
throw new \RuntimeException('Aucun entrepôt disponible pour créer la réception de marchandise.');
|
||||
}
|
||||
|
||||
$receiptNumber = 'GR-' . now()->format('Ym') . '-' . str_pad((string)$purchaseOrder->id, 4, '0', STR_PAD_LEFT);
|
||||
|
||||
$lines = collect($purchaseOrder->lines ?? [])
|
||||
->filter(fn($line) => !empty($line->product_id))
|
||||
->map(function ($line) {
|
||||
return [
|
||||
'product_id' => (int)$line->product_id,
|
||||
'packaging_id' => null,
|
||||
'packages_qty_received' => null,
|
||||
'units_qty_received' => (float)$line->quantity,
|
||||
'qty_received_base' => (float)$line->quantity,
|
||||
'unit_price' => (float)$line->unit_price,
|
||||
'unit_price_per_package' => null,
|
||||
'tva_rate_id' => null,
|
||||
];
|
||||
})
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$this->goodsReceiptRepository->create([
|
||||
'purchase_order_id' => $purchaseOrder->id,
|
||||
'warehouse_id' => (int)$warehouseId,
|
||||
'receipt_number' => $receiptNumber,
|
||||
'receipt_date' => now()->toDateString(),
|
||||
'status' => 'draft',
|
||||
'lines' => $lines,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified purchase order.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->purchaseOrderRepository->delete($id);
|
||||
|
||||
if (!$deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Commande fournisseur non trouvée ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Commande fournisseur supprimée avec succès.',
|
||||
], 200);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
Log::error('Error deleting purchase order: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'purchase_order_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression de la commande fournisseur.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
233
thanasoft-back/app/Http/Controllers/Api/QuoteController.php
Normal file
233
thanasoft-back/app/Http/Controllers/Api/QuoteController.php
Normal file
@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreQuoteRequest;
|
||||
use App\Http\Requests\UpdateQuoteRequest;
|
||||
use App\Http\Resources\QuoteResource;
|
||||
use App\Models\Quote;
|
||||
use App\Repositories\QuoteRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Http\Request;
|
||||
use Barryvdh\DomPDF\Facade\Pdf;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use App\Mail\DocumentMail;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class QuoteController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
protected QuoteRepositoryInterface $quoteRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of quotes.
|
||||
*/
|
||||
public function index(): AnonymousResourceCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$quotes = $this->quoteRepository->all();
|
||||
return QuoteResource::collection($quotes);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching quotes: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des devis.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created quote.
|
||||
*/
|
||||
public function store(StoreQuoteRequest $request): QuoteResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$quote = $this->quoteRepository->create($request->validated());
|
||||
return new QuoteResource($quote);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating quote: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création du devis.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified quote.
|
||||
*/
|
||||
public function show(string $id): QuoteResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$quote = $this->quoteRepository->find($id);
|
||||
|
||||
if (! $quote) {
|
||||
return response()->json([
|
||||
'message' => 'Devis non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new QuoteResource($quote);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching quote: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'quote_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du devis.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified quote.
|
||||
*/
|
||||
public function update(UpdateQuoteRequest $request, string $id): QuoteResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->quoteRepository->update($id, $request->validated());
|
||||
|
||||
if (! $updated) {
|
||||
return response()->json([
|
||||
'message' => 'Devis non trouvé ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$quote = $this->quoteRepository->find($id);
|
||||
return new QuoteResource($quote);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating quote: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'quote_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du devis.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified quote.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->quoteRepository->delete($id);
|
||||
|
||||
if (! $deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Devis non trouvé ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Devis supprimé avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting quote: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'quote_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression du devis.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the quote by email to the client.
|
||||
*/
|
||||
public function sendByEmail(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$quote = $this->quoteRepository->find($id);
|
||||
|
||||
if (!$quote) {
|
||||
return response()->json(['message' => 'Devis non trouvé.'], 404);
|
||||
}
|
||||
|
||||
if (!$quote->client || !$quote->client->email) {
|
||||
return response()->json(['message' => 'Le client n\'a pas d\'adresse email.'], 422);
|
||||
}
|
||||
|
||||
// Load lines to ensure they are available in the view
|
||||
$quote->load('lines');
|
||||
|
||||
// Generate PDF
|
||||
$pdfContent = Pdf::loadView('pdf.quote_pdf', ['quote' => $quote])->output();
|
||||
|
||||
// Send Email
|
||||
Mail::to($quote->client->email)->send(new DocumentMail($quote, 'quote', $pdfContent));
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Le devis a été envoyé avec succès à ' . $quote->client->email,
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error sending quote email: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'quote_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de l\'envoi de l\'email.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download the quote as a PDF.
|
||||
*/
|
||||
public function downloadPdf(string $id): Response|JsonResponse
|
||||
{
|
||||
try {
|
||||
$quote = Quote::with(['client', 'group', 'lines'])->find($id);
|
||||
|
||||
if (! $quote) {
|
||||
return response()->json([
|
||||
'message' => 'Devis non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$pdf = Pdf::loadView('pdf.quote_pdf', ['quote' => $quote]);
|
||||
|
||||
return $pdf->download('Devis_' . $quote->reference . '.pdf');
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error downloading quote PDF: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'quote_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la generation du PDF.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
134
thanasoft-back/app/Http/Controllers/Api/StockItemController.php
Normal file
134
thanasoft-back/app/Http/Controllers/Api/StockItemController.php
Normal file
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreStockItemRequest;
|
||||
use App\Http\Requests\UpdateStockItemRequest;
|
||||
use App\Http\Resources\StockItemResource;
|
||||
use App\Repositories\StockItemRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class StockItemController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly StockItemRepositoryInterface $stockItemRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of stock items.
|
||||
*/
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$items = $this->stockItemRepository->all();
|
||||
return response()->json([
|
||||
'data' => StockItemResource::collection($items),
|
||||
'status' => 'success'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching stock items: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des stocks.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created stock item.
|
||||
*/
|
||||
public function store(StoreStockItemRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$item = $this->stockItemRepository->create($request->validated());
|
||||
return response()->json([
|
||||
'data' => new StockItemResource($item),
|
||||
'message' => 'Stock initialisé avec succès.',
|
||||
'status' => 'success'
|
||||
], 201);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating stock item: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de l\'initialisation du stock.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified stock item.
|
||||
*/
|
||||
public function show(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$item = $this->stockItemRepository->find((int) $id);
|
||||
if (!$item) {
|
||||
return response()->json(['message' => 'Stock non trouvé.'], 404);
|
||||
}
|
||||
return response()->json([
|
||||
'data' => new StockItemResource($item),
|
||||
'status' => 'success'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching stock item: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du stock.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified stock item.
|
||||
*/
|
||||
public function update(UpdateStockItemRequest $request, string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->stockItemRepository->update((int) $id, $request->validated());
|
||||
if (!$updated) {
|
||||
return response()->json(['message' => 'Stock non trouvé ou échec de la mise à jour.'], 404);
|
||||
}
|
||||
$item = $this->stockItemRepository->find((int) $id);
|
||||
return response()->json([
|
||||
'data' => new StockItemResource($item),
|
||||
'message' => 'Stock mis à jour avec succès.',
|
||||
'status' => 'success'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating stock item: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du stock.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified stock item.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->stockItemRepository->delete((int) $id);
|
||||
if (!$deleted) {
|
||||
return response()->json(['message' => 'Stock non trouvé ou échec de la suppression.'], 404);
|
||||
}
|
||||
return response()->json([
|
||||
'message' => 'Stock supprimé avec succès.',
|
||||
'status' => 'success'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting stock item: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression du stock.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreStockMoveRequest;
|
||||
use App\Http\Resources\StockMoveResource;
|
||||
use App\Repositories\StockMoveRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class StockMoveController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly StockMoveRepositoryInterface $stockMoveRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of stock moves.
|
||||
*/
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$moves = $this->stockMoveRepository->all();
|
||||
return response()->json([
|
||||
'data' => StockMoveResource::collection($moves),
|
||||
'status' => 'success'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching stock moves: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des mouvements de stock.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created stock move.
|
||||
*/
|
||||
public function store(StoreStockMoveRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$move = $this->stockMoveRepository->create($request->validated());
|
||||
return response()->json([
|
||||
'data' => new StockMoveResource($move),
|
||||
'message' => 'Mouvement de stock enregistré avec succès.',
|
||||
'status' => 'success'
|
||||
], 201);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating stock move: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de l\'enregistrement du mouvement de stock.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified stock move.
|
||||
*/
|
||||
public function show(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$move = $this->stockMoveRepository->find((int) $id);
|
||||
if (!$move) {
|
||||
return response()->json(['message' => 'Mouvement de stock non trouvé.'], 404);
|
||||
}
|
||||
return response()->json([
|
||||
'data' => new StockMoveResource($move),
|
||||
'status' => 'success'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching stock move: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du mouvement de stock.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,357 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreThanatopractitionerRequest;
|
||||
use App\Http\Requests\UpdateThanatopractitionerRequest;
|
||||
use App\Http\Resources\Employee\ThanatopractitionerResource;
|
||||
use App\Http\Resources\Employee\ThanatopractitionerCollection;
|
||||
use App\Repositories\ThanatopractitionerRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ThanatopractitionerController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ThanatopractitionerRepositoryInterface $thanatopractitionerRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of thanatopractitioners (paginated).
|
||||
*/
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = (int) $request->get('per_page', 15);
|
||||
|
||||
$filters = [
|
||||
'search' => $request->get('search'),
|
||||
'valid_authorization' => $request->get('valid_authorization'),
|
||||
'sort_by' => $request->get('sort_by', 'created_at'),
|
||||
'sort_direction' => $request->get('sort_direction', 'desc'),
|
||||
];
|
||||
|
||||
// Remove null filters
|
||||
$filters = array_filter($filters, function ($value) {
|
||||
return $value !== null && $value !== '';
|
||||
});
|
||||
|
||||
$result = $this->thanatopractitionerRepository->getPaginated($perPage, $filters);
|
||||
|
||||
return response()->json([
|
||||
'data' => new ThanatopractitionerCollection($result['thanatopractitioners']),
|
||||
'pagination' => $result['pagination'],
|
||||
'message' => 'Thanatopractitioners récupérés avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching thanatopractitioners: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des thanatopractitioners.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display paginated thanatopractitioners.
|
||||
*/
|
||||
public function paginated(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$perPage = (int) $request->get('per_page', 15);
|
||||
$result = $this->thanatopractitionerRepository->getPaginated($perPage, []);
|
||||
|
||||
return response()->json([
|
||||
'data' => new ThanatopractitionerCollection($result['thanatopractitioners']),
|
||||
'pagination' => $result['pagination'],
|
||||
'message' => 'Thanatopractitioners récupérés avec succès.',
|
||||
], 200);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching paginated thanatopractitioners: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des thanatopractitioners.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get thanatopractitioners with valid authorization.
|
||||
*/
|
||||
public function withValidAuthorization(): ThanatopractitionerCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$thanatopractitioners = $this->thanatopractitionerRepository->getWithValidAuthorization();
|
||||
return new ThanatopractitionerCollection($thanatopractitioners);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching thanatopractitioners with valid authorization: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des thanatopractitioners avec autorisation valide.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get thanatopractitioners with expired authorization.
|
||||
*/
|
||||
public function withExpiredAuthorization(): ThanatopractitionerCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$thanatopractitioners = $this->thanatopractitionerRepository->getWithExpiredAuthorization();
|
||||
return new ThanatopractitionerCollection($thanatopractitioners);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching thanatopractitioners with expired authorization: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des thanatopractitioners avec autorisation expirée.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get thanatopractitioners with their complete data.
|
||||
*/
|
||||
public function withRelations(): ThanatopractitionerCollection|JsonResponse
|
||||
{
|
||||
try {
|
||||
$thanatopractitioners = $this->thanatopractitionerRepository->getWithRelations();
|
||||
return new ThanatopractitionerCollection($thanatopractitioners);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching thanatopractitioners with relations: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des thanatopractitioners avec relations.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get thanatopractitioner statistics.
|
||||
*/
|
||||
public function statistics(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$statistics = $this->thanatopractitionerRepository->getStatistics();
|
||||
|
||||
return response()->json([
|
||||
'data' => $statistics,
|
||||
'message' => 'Statistiques des thanatopractitioners récupérées avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching thanatopractitioner statistics: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des statistiques.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created thanatopractitioner.
|
||||
*/
|
||||
public function store(StoreThanatopractitionerRequest $request): ThanatopractitionerResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$thanatopractitioner = $this->thanatopractitionerRepository->create($request->validated());
|
||||
return new ThanatopractitionerResource($thanatopractitioner);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating thanatopractitioner: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création du thanatopractitioner.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified thanatopractitioner.
|
||||
*/
|
||||
public function show(string $id): ThanatopractitionerResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$thanatopractitioner = $this->thanatopractitionerRepository->findById((int) $id);
|
||||
|
||||
if (!$thanatopractitioner) {
|
||||
return response()->json([
|
||||
'message' => 'Thanatopractitioner non trouvé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new ThanatopractitionerResource($thanatopractitioner);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching thanatopractitioner: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'thanatopractitioner_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du thanatopractitioner.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a thanatopractitioner by employee ID.
|
||||
*/
|
||||
public function findByEmployee(string $employeeId): ThanatopractitionerResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$thanatopractitioner = $this->thanatopractitionerRepository->findByEmployeeId((int) $employeeId);
|
||||
|
||||
if (!$thanatopractitioner) {
|
||||
return response()->json([
|
||||
'message' => 'Thanatopractitioner non trouvé pour cet employé.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return new ThanatopractitionerResource($thanatopractitioner);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching thanatopractitioner by employee: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'employee_id' => $employeeId,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du thanatopractitioner.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search thanatopractitioners by employee name.
|
||||
*/
|
||||
public function searchByEmployeeName(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$query = $request->get('query', '');
|
||||
|
||||
if (strlen($query) < 2) {
|
||||
return response()->json([
|
||||
'data' => [],
|
||||
'message' => 'Veuillez entrer au moins 2 caractères pour la recherche.',
|
||||
], 200);
|
||||
}
|
||||
|
||||
$thanatopractitioners = $this->thanatopractitionerRepository->searchByEmployeeName($query);
|
||||
|
||||
return response()->json([
|
||||
'data' => new ThanatopractitionerCollection($thanatopractitioners),
|
||||
'message' => 'Recherche effectuée avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error searching thanatopractitioners by employee name: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'query' => $request->get('query'),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la recherche.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified thanatopractitioner.
|
||||
*/
|
||||
public function update(UpdateThanatopractitionerRequest $request, string $id): ThanatopractitionerResource|JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->thanatopractitionerRepository->update($id, $request->validated());
|
||||
|
||||
if (!$updated) {
|
||||
return response()->json([
|
||||
'message' => 'Thanatopractitioner non trouvé ou échec de la mise à jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$thanatopractitioner = $this->thanatopractitionerRepository->find($id);
|
||||
return new ThanatopractitionerResource($thanatopractitioner);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating thanatopractitioner: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'thanatopractitioner_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du thanatopractitioner.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified thanatopractitioner.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->thanatopractitionerRepository->delete($id);
|
||||
|
||||
if (!$deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Thanatopractitioner non trouvé ou échec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Thanatopractitioner supprimé avec succès.',
|
||||
], 200);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting thanatopractitioner: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'trace' => $e->getTraceAsString(),
|
||||
'thanatopractitioner_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression du thanatopractitioner.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
133
thanasoft-back/app/Http/Controllers/Api/TvaRateController.php
Normal file
133
thanasoft-back/app/Http/Controllers/Api/TvaRateController.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreTvaRateRequest;
|
||||
use App\Http\Requests\UpdateTvaRateRequest;
|
||||
use App\Http\Resources\TvaRateResource;
|
||||
use App\Repositories\TvaRateRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class TvaRateController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly TvaRateRepositoryInterface $tvaRateRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of TVA rates.
|
||||
*/
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$tvaRates = $this->tvaRateRepository->all();
|
||||
return response()->json([
|
||||
'data' => TvaRateResource::collection($tvaRates),
|
||||
'status' => 'success'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching TVA rates: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des taux de TVA.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created TVA rate.
|
||||
*/
|
||||
public function store(StoreTvaRateRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$tvaRate = $this->tvaRateRepository->create($request->validated());
|
||||
return response()->json([
|
||||
'data' => new TvaRateResource($tvaRate),
|
||||
'message' => 'Taux de TVA créé avec succès.',
|
||||
'status' => 'success'
|
||||
], 201);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating TVA rate: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création du taux de TVA.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified TVA rate.
|
||||
*/
|
||||
public function show(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$tvaRate = $this->tvaRateRepository->find((int) $id);
|
||||
if (!$tvaRate) {
|
||||
return response()->json(['message' => 'Taux de TVA non trouvé.'], 404);
|
||||
}
|
||||
return response()->json([
|
||||
'data' => new TvaRateResource($tvaRate),
|
||||
'status' => 'success'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching TVA rate: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération du taux de TVA.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified TVA rate.
|
||||
*/
|
||||
public function update(UpdateTvaRateRequest $request, string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->tvaRateRepository->update((int) $id, $request->validated());
|
||||
if (!$updated) {
|
||||
return response()->json(['message' => 'Taux de TVA non trouvé ou échec de la mise à jour.'], 404);
|
||||
}
|
||||
$tvaRate = $this->tvaRateRepository->find((int) $id);
|
||||
return response()->json([
|
||||
'data' => new TvaRateResource($tvaRate),
|
||||
'message' => 'Taux de TVA mis à jour avec succès.',
|
||||
'status' => 'success'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating TVA rate: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour du taux de TVA.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified TVA rate.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->tvaRateRepository->delete((int) $id);
|
||||
if (!$deleted) {
|
||||
return response()->json(['message' => 'Taux de TVA non trouvé ou échec de la suppression.'], 404);
|
||||
}
|
||||
return response()->json([
|
||||
'message' => 'Taux de TVA supprimé avec succès.',
|
||||
'status' => 'success'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting TVA rate: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression du taux de TVA.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
178
thanasoft-back/app/Http/Controllers/Api/UserController.php
Normal file
178
thanasoft-back/app/Http/Controllers/Api/UserController.php
Normal file
@ -0,0 +1,178 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreUserRequest;
|
||||
use App\Http\Requests\UpdateUserRequest;
|
||||
use App\Models\User;
|
||||
use App\Repositories\UserRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UserRepositoryInterface $userRepository
|
||||
) {
|
||||
}
|
||||
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$email = request()->query('email');
|
||||
|
||||
if ($email) {
|
||||
$user = User::query()->with(['roles', 'permissions'])->where('email', $email)->first();
|
||||
|
||||
return response()->json([
|
||||
'data' => $user,
|
||||
'message' => $user
|
||||
? 'Utilisateur recupere avec succes.'
|
||||
: 'Aucun utilisateur trouve pour cet email.',
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'data' => $this->userRepository->all()->load(['roles', 'permissions'])->sortBy('name')->values(),
|
||||
'message' => 'Utilisateurs recuperes avec succes.',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching users: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la recuperation des utilisateurs.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function store(StoreUserRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$user = $this->userRepository->create($request->validated());
|
||||
|
||||
return response()->json([
|
||||
'data' => $user->load('roles', 'permissions'),
|
||||
'message' => 'Utilisateur cree avec succes.',
|
||||
], 201);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating user: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la creation de l\'utilisateur.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function show(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$user = $this->userRepository->find($id);
|
||||
|
||||
if (! $user) {
|
||||
return response()->json([
|
||||
'message' => 'Utilisateur non trouve.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'data' => $user->load('roles', 'permissions'),
|
||||
'message' => 'Utilisateur recupere avec succes.',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching user: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'user_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la recuperation de l\'utilisateur.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function update(UpdateUserRequest $request, string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$user = $this->userRepository->find($id);
|
||||
|
||||
if (! $user) {
|
||||
return response()->json([
|
||||
'message' => 'Utilisateur non trouve ou echec de la mise a jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$validated = $request->validated();
|
||||
$clearPassword = (bool) ($validated['clear_password'] ?? false);
|
||||
|
||||
unset($validated['clear_password']);
|
||||
|
||||
if ($clearPassword) {
|
||||
$validated['password'] = null;
|
||||
} elseif (empty($validated['password'])) {
|
||||
unset($validated['password']);
|
||||
}
|
||||
|
||||
$updated = $this->userRepository->update($id, $validated);
|
||||
|
||||
if (! $updated) {
|
||||
return response()->json([
|
||||
'message' => 'Utilisateur non trouve ou echec de la mise a jour.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'data' => $user->fresh()->load('roles', 'permissions'),
|
||||
'message' => 'Utilisateur mis a jour avec succes.',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating user: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'user_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise a jour de l\'utilisateur.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->userRepository->delete($id);
|
||||
|
||||
if (! $deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Utilisateur non trouve ou echec de la suppression.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Utilisateur supprime avec succes.',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting user: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'user_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression de l\'utilisateur.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
144
thanasoft-back/app/Http/Controllers/Api/VehicleController.php
Normal file
144
thanasoft-back/app/Http/Controllers/Api/VehicleController.php
Normal file
@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreVehicleRequest;
|
||||
use App\Http\Requests\UpdateVehicleRequest;
|
||||
use App\Http\Resources\Vehicle\VehicleResource;
|
||||
use App\Repositories\VehicleRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class VehicleController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly VehicleRepositoryInterface $vehicleRepository
|
||||
) {
|
||||
}
|
||||
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$vehicles = $this->vehicleRepository->paginate(
|
||||
(int) $request->integer('per_page', 15),
|
||||
$request->only(['search', 'status', 'vehicle_type', 'sort_by', 'sort_direction'])
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'data' => VehicleResource::collection($vehicles->items()),
|
||||
'meta' => [
|
||||
'current_page' => $vehicles->currentPage(),
|
||||
'last_page' => $vehicles->lastPage(),
|
||||
'per_page' => $vehicles->perPage(),
|
||||
'total' => $vehicles->total(),
|
||||
],
|
||||
'status' => 'success',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching vehicles: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'An error occurred while fetching vehicles.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function store(StoreVehicleRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$vehicle = $this->vehicleRepository->create($request->validated());
|
||||
|
||||
return response()->json([
|
||||
'data' => new VehicleResource($vehicle->load('primaryUser')),
|
||||
'message' => 'Vehicle created successfully.',
|
||||
'status' => 'success',
|
||||
], 201);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating vehicle: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'An error occurred while creating the vehicle.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function show(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$vehicle = $this->vehicleRepository->find((int) $id);
|
||||
|
||||
if (! $vehicle) {
|
||||
return response()->json(['message' => 'Vehicle not found.'], 404);
|
||||
}
|
||||
|
||||
$vehicle->load(['primaryUser', 'convoys']);
|
||||
|
||||
return response()->json([
|
||||
'data' => new VehicleResource($vehicle),
|
||||
'status' => 'success',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching vehicle: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'An error occurred while fetching the vehicle.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function update(UpdateVehicleRequest $request, string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->vehicleRepository->update((int) $id, $request->validated());
|
||||
|
||||
if (! $updated) {
|
||||
return response()->json(['message' => 'Vehicle not found or update failed.'], 404);
|
||||
}
|
||||
|
||||
$vehicle = $this->vehicleRepository->find((int) $id);
|
||||
|
||||
return response()->json([
|
||||
'data' => new VehicleResource($vehicle->load('primaryUser')),
|
||||
'message' => 'Vehicle updated successfully.',
|
||||
'status' => 'success',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating vehicle: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'An error occurred while updating the vehicle.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->vehicleRepository->delete((int) $id);
|
||||
|
||||
if (! $deleted) {
|
||||
return response()->json(['message' => 'Vehicle not found or delete failed.'], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Vehicle deleted successfully.',
|
||||
'status' => 'success',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting vehicle: ' . $e->getMessage());
|
||||
|
||||
return response()->json([
|
||||
'message' => 'An error occurred while deleting the vehicle.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
174
thanasoft-back/app/Http/Controllers/Api/WarehouseController.php
Normal file
174
thanasoft-back/app/Http/Controllers/Api/WarehouseController.php
Normal file
@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreWarehouseRequest;
|
||||
use App\Http\Requests\UpdateWarehouseRequest;
|
||||
use App\Http\Resources\WarehouseResource;
|
||||
use App\Repositories\WarehouseRepositoryInterface;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class WarehouseController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly WarehouseRepositoryInterface $warehouseRepository
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of warehouses.
|
||||
*/
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$warehouses = $this->warehouseRepository->all();
|
||||
return response()->json([
|
||||
'data' => WarehouseResource::collection($warehouses),
|
||||
'status' => 'success'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching warehouses: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération des entrepôts.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created warehouse.
|
||||
*/
|
||||
public function store(StoreWarehouseRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$warehouse = $this->warehouseRepository->create($request->validated());
|
||||
return response()->json([
|
||||
'data' => new WarehouseResource($warehouse),
|
||||
'message' => 'Entrepôt créé avec succès.',
|
||||
'status' => 'success'
|
||||
], 201);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error creating warehouse: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la création de l\'entrepôt.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display specified warehouse.
|
||||
*/
|
||||
public function show(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$warehouse = $this->warehouseRepository->find((int) $id);
|
||||
if (!$warehouse) {
|
||||
return response()->json(['message' => 'Entrepôt non trouvé.'], 404);
|
||||
}
|
||||
return response()->json([
|
||||
'data' => new WarehouseResource($warehouse),
|
||||
'status' => 'success'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching warehouse: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la récupération de l\'entrepôt.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update specified warehouse.
|
||||
*/
|
||||
public function update(UpdateWarehouseRequest $request, string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$updated = $this->warehouseRepository->update((int) $id, $request->validated());
|
||||
if (!$updated) {
|
||||
return response()->json(['message' => 'Entrepôt non trouvé ou échec de la mise à jour.'], 404);
|
||||
}
|
||||
$warehouse = $this->warehouseRepository->find((int) $id);
|
||||
return response()->json([
|
||||
'data' => new WarehouseResource($warehouse),
|
||||
'message' => 'Entrepôt mis à jour avec succès.',
|
||||
'status' => 'success'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating warehouse: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise à jour de l\'entrepôt.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove specified warehouse.
|
||||
*/
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$deleted = $this->warehouseRepository->delete((int) $id);
|
||||
if (!$deleted) {
|
||||
return response()->json(['message' => 'Entrepôt non trouvé ou échec de la suppression.'], 404);
|
||||
}
|
||||
return response()->json([
|
||||
'message' => 'Entrepôt supprimé avec succès.',
|
||||
'status' => 'success'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting warehouse: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression de l\'entrepôt.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search warehouses by name.
|
||||
*/
|
||||
public function searchBy(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$name = $request->query('name');
|
||||
$exactMatch = $request->query('exact_match', false);
|
||||
|
||||
if (empty($name)) {
|
||||
return response()->json([
|
||||
'data' => [],
|
||||
'count' => 0,
|
||||
'message' => 'Le paramètre de recherche est requis.'
|
||||
], 400);
|
||||
}
|
||||
|
||||
$warehouses = $this->warehouseRepository->all();
|
||||
|
||||
$filtered = $warehouses->filter(function ($warehouse) use ($name, $exactMatch) {
|
||||
if ($exactMatch) {
|
||||
return strtolower($warehouse->name) === strtolower($name);
|
||||
}
|
||||
return stripos($warehouse->name, $name) !== false;
|
||||
});
|
||||
|
||||
return response()->json([
|
||||
'data' => WarehouseResource::collection($filtered),
|
||||
'count' => $filtered->count(),
|
||||
'message' => 'Recherche effectuée avec succès.'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error searching warehouses: ' . $e->getMessage());
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la recherche des entrepôts.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
470
thanasoft-back/app/Http/Controllers/Api/WebmailController.php
Normal file
470
thanasoft-back/app/Http/Controllers/Api/WebmailController.php
Normal file
@ -0,0 +1,470 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ReceiveWebmailMessageRequest;
|
||||
use App\Http\Requests\SendWebmailMessageRequest;
|
||||
use App\Http\Requests\UpsertUserMailboxSettingRequest;
|
||||
use App\Http\Requests\UpdateWebmailMessageRequest;
|
||||
use App\Http\Resources\Webmail\UserMailboxSettingResource;
|
||||
use App\Http\Resources\Webmail\WebmailMessageResource;
|
||||
use App\Models\UserMailboxSetting;
|
||||
use App\Models\WebmailMessage;
|
||||
use App\Repositories\WebmailMessageRepositoryInterface;
|
||||
use App\Services\WebmailService;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class WebmailController extends Controller
|
||||
{
|
||||
public function __construct(
|
||||
private readonly WebmailMessageRepositoryInterface $webmailRepository,
|
||||
private readonly WebmailService $webmailService,
|
||||
) {
|
||||
}
|
||||
|
||||
public function index(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$user = Auth::user();
|
||||
|
||||
if (! $user) {
|
||||
return response()->json([
|
||||
'message' => 'Utilisateur non authentifie.',
|
||||
], 401);
|
||||
}
|
||||
|
||||
$messages = $this->webmailRepository->paginateForUser(
|
||||
(int) $user->id,
|
||||
[
|
||||
'folder' => $request->query('folder'),
|
||||
'status' => $request->query('status'),
|
||||
'search' => $request->query('search'),
|
||||
'unread' => $request->has('unread') ? $request->boolean('unread') : null,
|
||||
'starred' => $request->has('starred') ? $request->boolean('starred') : null,
|
||||
],
|
||||
max(1, (int) $request->integer('per_page', 15)),
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'data' => $messages->getCollection()
|
||||
->map(fn (WebmailMessage $message): array => (new WebmailMessageResource($message))->resolve())
|
||||
->values(),
|
||||
'meta' => [
|
||||
'current_page' => $messages->currentPage(),
|
||||
'last_page' => $messages->lastPage(),
|
||||
'per_page' => $messages->perPage(),
|
||||
'total' => $messages->total(),
|
||||
],
|
||||
'message' => 'Messages recuperes avec succes.',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching webmail messages: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la recuperation des messages.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function stats(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$user = Auth::user();
|
||||
|
||||
if (! $user) {
|
||||
return response()->json([
|
||||
'message' => 'Utilisateur non authentifie.',
|
||||
], 401);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'data' => $this->webmailRepository->statsForUser((int) $user->id),
|
||||
'message' => 'Statistiques webmail recuperees avec succes.',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching webmail stats: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la recuperation des statistiques webmail.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function show(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$user = Auth::user();
|
||||
|
||||
if (! $user) {
|
||||
return response()->json([
|
||||
'message' => 'Utilisateur non authentifie.',
|
||||
], 401);
|
||||
}
|
||||
|
||||
$message = $this->webmailRepository->findForUser($id, (int) $user->id);
|
||||
|
||||
if (! $message) {
|
||||
return response()->json([
|
||||
'message' => 'Message non trouve.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'data' => (new WebmailMessageResource($message))->resolve(),
|
||||
'message' => 'Message recupere avec succes.',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error fetching webmail message: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'message_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la recuperation du message.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function send(SendWebmailMessageRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$user = Auth::user();
|
||||
|
||||
if (! $user) {
|
||||
return response()->json([
|
||||
'message' => 'Utilisateur non authentifie.',
|
||||
], 401);
|
||||
}
|
||||
|
||||
$message = $this->webmailService->send($request->validated(), $user);
|
||||
|
||||
return response()->json([
|
||||
'data' => (new WebmailMessageResource($message))->resolve(),
|
||||
'message' => 'Email envoye avec succes.',
|
||||
], 201);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error sending webmail message: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de l\'envoi de l\'email.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function receive(ReceiveWebmailMessageRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$user = Auth::user();
|
||||
|
||||
if (! $user) {
|
||||
return response()->json([
|
||||
'message' => 'Utilisateur non authentifie.',
|
||||
], 401);
|
||||
}
|
||||
|
||||
$message = $this->webmailService->receive($request->validated(), $user);
|
||||
|
||||
return response()->json([
|
||||
'data' => (new WebmailMessageResource($message))->resolve(),
|
||||
'message' => 'Email recu et enregistre avec succes.',
|
||||
], 201);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error receiving webmail message: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de l\'enregistrement du message recu.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function syncMailtrap(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$user = Auth::user();
|
||||
|
||||
if (! $user) {
|
||||
return response()->json([
|
||||
'message' => 'Utilisateur non authentifie.',
|
||||
], 401);
|
||||
}
|
||||
|
||||
$result = $this->webmailService->syncMailtrapInbox(
|
||||
$user,
|
||||
max(1, min(50, (int) $request->integer('limit', 30)))
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'data' => $result,
|
||||
'message' => 'Synchronisation Mailtrap terminee avec succes.',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error syncing Mailtrap webmail messages: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'user_id' => Auth::id(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la synchronisation Mailtrap.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function sync(Request $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$user = Auth::user();
|
||||
|
||||
if (! $user) {
|
||||
return response()->json([
|
||||
'message' => 'Utilisateur non authentifie.',
|
||||
], 401);
|
||||
}
|
||||
|
||||
$result = $this->webmailService->syncMailbox(
|
||||
$user->loadMissing('mailboxSetting'),
|
||||
max(1, min(50, (int) $request->integer('limit', 30)))
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'data' => $result,
|
||||
'message' => 'Synchronisation de la boite mail terminee avec succes.',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error syncing mailbox webmail messages: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'user_id' => Auth::id(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la synchronisation de la boite mail.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function testSmtp(): JsonResponse
|
||||
{
|
||||
try {
|
||||
$user = Auth::user();
|
||||
|
||||
if (! $user) {
|
||||
return response()->json([
|
||||
'message' => 'Utilisateur non authentifie.',
|
||||
], 401);
|
||||
}
|
||||
|
||||
$result = $this->webmailService->testSmtp($user->loadMissing('mailboxSetting'));
|
||||
|
||||
return response()->json([
|
||||
'data' => $result,
|
||||
'message' => 'Test SMTP envoye avec succes.',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error testing mailbox SMTP: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'user_id' => Auth::id(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors du test SMTP.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function mailboxSettings(): JsonResponse
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if (! $user) {
|
||||
return response()->json([
|
||||
'message' => 'Utilisateur non authentifie.',
|
||||
], 401);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'data' => $user->relationLoaded('mailboxSetting')
|
||||
? ($user->mailboxSetting ? (new UserMailboxSettingResource($user->mailboxSetting))->resolve() : null)
|
||||
: ($user->mailboxSetting ? (new UserMailboxSettingResource($user->mailboxSetting))->resolve() : null),
|
||||
'message' => 'Configuration mailbox recuperee avec succes.',
|
||||
]);
|
||||
}
|
||||
|
||||
public function upsertMailboxSettings(UpsertUserMailboxSettingRequest $request): JsonResponse
|
||||
{
|
||||
try {
|
||||
$user = Auth::user();
|
||||
|
||||
if (! $user) {
|
||||
return response()->json([
|
||||
'message' => 'Utilisateur non authentifie.',
|
||||
], 401);
|
||||
}
|
||||
|
||||
$validated = $request->validated();
|
||||
$clearImapPassword = (bool) ($validated['clear_imap_password'] ?? false);
|
||||
$clearSmtpPassword = (bool) ($validated['clear_smtp_password'] ?? false);
|
||||
|
||||
unset($validated['clear_imap_password'], $validated['clear_smtp_password']);
|
||||
|
||||
if ($clearImapPassword) {
|
||||
$validated['imap_password'] = null;
|
||||
} elseif (array_key_exists('imap_password', $validated) && $validated['imap_password'] === null) {
|
||||
unset($validated['imap_password']);
|
||||
}
|
||||
|
||||
if ($clearSmtpPassword) {
|
||||
$validated['smtp_password'] = null;
|
||||
} elseif (array_key_exists('smtp_password', $validated) && $validated['smtp_password'] === null) {
|
||||
unset($validated['smtp_password']);
|
||||
}
|
||||
|
||||
/** @var UserMailboxSetting $settings */
|
||||
$settings = UserMailboxSetting::query()->updateOrCreate(
|
||||
['user_id' => $user->id],
|
||||
$validated,
|
||||
);
|
||||
|
||||
return response()->json([
|
||||
'data' => (new UserMailboxSettingResource($settings))->resolve(),
|
||||
'message' => 'Configuration mailbox mise a jour avec succes.',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating mailbox settings: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'user_id' => Auth::id(),
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise a jour de la configuration mailbox.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function update(UpdateWebmailMessageRequest $request, string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$user = Auth::user();
|
||||
|
||||
if (! $user) {
|
||||
return response()->json([
|
||||
'message' => 'Utilisateur non authentifie.',
|
||||
], 401);
|
||||
}
|
||||
|
||||
$message = $this->webmailRepository->findForUser($id, (int) $user->id);
|
||||
|
||||
if (! $message) {
|
||||
return response()->json([
|
||||
'message' => 'Message non trouve.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$validated = $request->validated();
|
||||
|
||||
if (array_key_exists('is_read', $validated)) {
|
||||
$validated['read_at'] = $validated['is_read'] ? now() : null;
|
||||
unset($validated['is_read']);
|
||||
}
|
||||
|
||||
if (array_key_exists('is_starred', $validated)) {
|
||||
$validated['starred_at'] = $validated['is_starred'] ? now() : null;
|
||||
unset($validated['is_starred']);
|
||||
}
|
||||
|
||||
$updated = $this->webmailRepository->update($id, $validated);
|
||||
|
||||
if (! $updated) {
|
||||
return response()->json([
|
||||
'message' => 'Echec de la mise a jour du message.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
/** @var WebmailMessage $freshMessage */
|
||||
$freshMessage = $this->webmailRepository->findForUser($id, (int) $user->id);
|
||||
|
||||
return response()->json([
|
||||
'data' => (new WebmailMessageResource($freshMessage))->resolve(),
|
||||
'message' => 'Message mis a jour avec succes.',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error updating webmail message: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'message_id' => $id,
|
||||
'data' => $request->validated(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la mise a jour du message.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(string $id): JsonResponse
|
||||
{
|
||||
try {
|
||||
$user = Auth::user();
|
||||
|
||||
if (! $user) {
|
||||
return response()->json([
|
||||
'message' => 'Utilisateur non authentifie.',
|
||||
], 401);
|
||||
}
|
||||
|
||||
$message = $this->webmailRepository->findForUser($id, (int) $user->id);
|
||||
|
||||
if (! $message) {
|
||||
return response()->json([
|
||||
'message' => 'Message non trouve.',
|
||||
], 404);
|
||||
}
|
||||
|
||||
$deleted = $this->webmailRepository->delete($id);
|
||||
|
||||
if (! $deleted) {
|
||||
return response()->json([
|
||||
'message' => 'Echec de la suppression du message.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Message supprime avec succes.',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error deleting webmail message: ' . $e->getMessage(), [
|
||||
'exception' => $e,
|
||||
'message_id' => $id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Une erreur est survenue lors de la suppression du message.',
|
||||
'error' => config('app.debug') ? $e->getMessage() : null,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class AssignClientsToGroupRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'client_ids' => 'required|array|min:1',
|
||||
'client_ids.*' => 'integer|distinct|exists:clients,id',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'client_ids.required' => 'La liste des clients est obligatoire.',
|
||||
'client_ids.array' => 'La liste des clients doit être un tableau.',
|
||||
'client_ids.min' => 'Veuillez sélectionner au moins un client.',
|
||||
'client_ids.*.integer' => 'Chaque ID client doit être un entier.',
|
||||
'client_ids.*.distinct' => 'Un client ne peut pas être envoyé plusieurs fois.',
|
||||
'client_ids.*.exists' => 'Un ou plusieurs clients sélectionnés sont introuvables.',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
70
thanasoft-back/app/Http/Requests/ClientCategoryRequest.php
Normal file
70
thanasoft-back/app/Http/Requests/ClientCategoryRequest.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ClientCategoryRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$categoryId = $this->route('client_category')?->id;
|
||||
|
||||
return [
|
||||
'name' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:255',
|
||||
Rule::unique('client_categories')->ignore($categoryId)
|
||||
],
|
||||
'slug' => [
|
||||
'nullable',
|
||||
'string',
|
||||
'max:255',
|
||||
'alpha_dash',
|
||||
Rule::unique('client_categories')->ignore($categoryId)
|
||||
],
|
||||
'description' => 'nullable|string|max:1000',
|
||||
'is_active' => 'boolean',
|
||||
'sort_order' => 'nullable|integer|min:0',
|
||||
];
|
||||
}
|
||||
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'category name',
|
||||
'slug' => 'URL slug',
|
||||
];
|
||||
}
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
// Generate slug from name if not provided and name exists
|
||||
if (!$this->slug && $this->name) {
|
||||
$this->merge([
|
||||
'slug' => \Str::slug($this->name),
|
||||
]);
|
||||
}
|
||||
|
||||
// Ensure boolean values are properly cast
|
||||
if ($this->has('is_active')) {
|
||||
$this->merge([
|
||||
'is_active' => filter_var($this->is_active, FILTER_VALIDATE_BOOLEAN),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class ReceiveWebmailMessageRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'from_email' => ['required', 'email:rfc,dns'],
|
||||
'from_name' => ['nullable', 'string', 'max:255'],
|
||||
'to' => ['required', 'array', 'min:1'],
|
||||
'to.*' => ['required', 'email:rfc,dns'],
|
||||
'cc' => ['nullable', 'array'],
|
||||
'cc.*' => ['email:rfc,dns'],
|
||||
'bcc' => ['nullable', 'array'],
|
||||
'bcc.*' => ['email:rfc,dns'],
|
||||
'subject' => ['nullable', 'string', 'max:255'],
|
||||
'body' => ['nullable', 'string'],
|
||||
'folder' => ['nullable', 'string', 'max:30'],
|
||||
'status' => ['nullable', 'string', 'max:30'],
|
||||
'received_at' => ['nullable', 'date'],
|
||||
'attachments' => ['nullable', 'array'],
|
||||
'metadata' => ['nullable', 'array'],
|
||||
'message_uid' => ['nullable', 'string', 'max:255'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class SendWebmailMessageRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'to' => ['required', 'array', 'min:1'],
|
||||
'to.*' => ['required', 'email:rfc,dns'],
|
||||
'cc' => ['nullable', 'array'],
|
||||
'cc.*' => ['email:rfc,dns'],
|
||||
'bcc' => ['nullable', 'array'],
|
||||
'bcc.*' => ['email:rfc,dns'],
|
||||
'subject' => ['nullable', 'string', 'max:255'],
|
||||
'body' => ['required', 'string'],
|
||||
'folder' => ['nullable', 'string', 'max:30'],
|
||||
'attachments' => ['nullable', 'array'],
|
||||
'metadata' => ['nullable', 'array'],
|
||||
'message_uid' => ['nullable', 'string', 'max:255'],
|
||||
];
|
||||
}
|
||||
}
|
||||
48
thanasoft-back/app/Http/Requests/StoreAvoirRequest.php
Normal file
48
thanasoft-back/app/Http/Requests/StoreAvoirRequest.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreAvoirRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'client_id' => 'required|exists:clients,id',
|
||||
'invoice_id' => 'nullable|exists:invoices,id',
|
||||
'group_id' => 'nullable|exists:client_groups,id',
|
||||
'status' => 'required|in:brouillon,emis,applique,annule',
|
||||
'avoir_date' => 'required|date',
|
||||
'due_date' => 'nullable|date|after_or_equal:avoir_date',
|
||||
'currency' => 'required|string|size:3',
|
||||
'total_ht' => 'required|numeric',
|
||||
'total_tva' => 'required|numeric',
|
||||
'total_ttc' => 'required|numeric',
|
||||
'reason_type' => 'required|in:remboursement_total,remboursement_partiel,reduction,erreur_facturation,retour_marchandise,accord_commercial,autre',
|
||||
'reason_description' => 'nullable|string',
|
||||
'e_invoice_status' => 'nullable|in:cree,transmis,accepte,refuse,en_litige,acquitte,archive',
|
||||
'refund_status' => 'nullable|in:non_rembourse,en_cours,partiellement_rembourse,rembourse,compense',
|
||||
'refund_date' => 'nullable|date',
|
||||
'refund_method' => 'nullable|in:virement,cheque,carte_credit,compensation_future,autre',
|
||||
'compensation_invoice_id' => 'nullable|exists:invoices,id',
|
||||
'compensation_amount' => 'nullable|numeric|min:0',
|
||||
'lines' => 'required|array|min:1',
|
||||
'lines.*.product_id' => 'nullable|exists:products,id',
|
||||
'lines.*.invoice_line_id' => 'nullable|exists:invoice_lines,id',
|
||||
'lines.*.description' => 'required|string',
|
||||
'lines.*.quantity' => 'required|numeric',
|
||||
'lines.*.unit_price' => 'required|numeric',
|
||||
'lines.*.tva_rate' => 'required|numeric',
|
||||
'lines.*.total_ht' => 'required|numeric',
|
||||
'lines.*.total_tva' => 'required|numeric',
|
||||
'lines.*.total_ttc' => 'required|numeric',
|
||||
'lines.*.notes' => 'nullable|string',
|
||||
];
|
||||
}
|
||||
}
|
||||
46
thanasoft-back/app/Http/Requests/StoreClientGroupRequest.php
Normal file
46
thanasoft-back/app/Http/Requests/StoreClientGroupRequest.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreClientGroupRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|max:191|unique:client_groups,name',
|
||||
'description' => 'nullable|string',
|
||||
'client_ids' => 'sometimes|array',
|
||||
'client_ids.*' => 'integer|distinct|exists:clients,id',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'name.required' => 'Le nom du groupe est obligatoire.',
|
||||
'name.string' => 'Le nom du groupe doit être une chaîne de caractères.',
|
||||
'name.max' => 'Le nom du groupe ne peut pas dépasser 191 caractères.',
|
||||
'name.unique' => 'Un groupe avec ce nom existe déjà.',
|
||||
'description.string' => 'La description doit être une chaîne de caractères.',
|
||||
'client_ids.array' => 'La liste des clients doit être un tableau.',
|
||||
'client_ids.*.integer' => 'Chaque ID client doit être un entier.',
|
||||
'client_ids.*.distinct' => 'Un client ne peut pas être sélectionné plusieurs fois.',
|
||||
'client_ids.*.exists' => 'Un ou plusieurs clients sélectionnés sont introuvables.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreClientLocationRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'client_id' => 'required|exists:clients,id',
|
||||
'name' => 'nullable|string|max:191',
|
||||
'address_line1' => 'nullable|string|max:255',
|
||||
'address_line2' => 'nullable|string|max:255',
|
||||
'postal_code' => 'nullable|string|max:20',
|
||||
'city' => 'nullable|string|max:191',
|
||||
'country_code' => 'nullable|string|size:2',
|
||||
'gps_lat' => 'nullable|numeric|between:-90,90',
|
||||
'gps_lng' => 'nullable|numeric|between:-180,180',
|
||||
'is_default' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'client_id.required' => 'Le client est obligatoire.',
|
||||
'client_id.exists' => 'Le client sélectionné n\'existe pas.',
|
||||
'name.max' => 'Le nom ne peut pas dépasser 191 caractères.',
|
||||
'address_line1.max' => 'L\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'address_line2.max' => 'Le complément d\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'postal_code.max' => 'Le code postal ne peut pas dépasser 20 caractères.',
|
||||
'city.max' => 'La ville ne peut pas dépasser 191 caractères.',
|
||||
'country_code.size' => 'Le code pays doit contenir 2 caractères.',
|
||||
'gps_lat.numeric' => 'La latitude doit être un nombre.',
|
||||
'gps_lat.between' => 'La latitude doit être comprise entre -90 et 90.',
|
||||
'gps_lng.numeric' => 'La longitude doit être un nombre.',
|
||||
'gps_lng.between' => 'La longitude doit être comprise entre -180 et 180.',
|
||||
'is_default.boolean' => 'Le statut par défaut doit être vrai ou faux.',
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator($validator)
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
if (empty($this->address_line1) && empty($this->postal_code) && empty($this->city)) {
|
||||
$validator->errors()->add(
|
||||
'general',
|
||||
'Au moins un champ d\'adresse (adresse, code postal ou ville) doit être renseigné.'
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
72
thanasoft-back/app/Http/Requests/StoreClientRequest.php
Normal file
72
thanasoft-back/app/Http/Requests/StoreClientRequest.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreClientRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'client_category_id' => 'nullable',
|
||||
'name' => 'required|string|max:255',
|
||||
'vat_number' => 'nullable|string|max:32',
|
||||
'siret' => 'nullable|string|max:20',
|
||||
'email' => 'nullable|email|max:191',
|
||||
'phone' => 'nullable|string|max:50',
|
||||
'billing_address_line1' => 'required|string|max:255',
|
||||
'billing_address_line2' => 'nullable|string|max:255',
|
||||
'billing_postal_code' => 'nullable|string|max:20',
|
||||
'billing_city' => 'nullable|string|max:191',
|
||||
'billing_country_code' => 'nullable|string|size:2',
|
||||
'group_id' => 'nullable|exists:client_groups,id',
|
||||
'notes' => 'nullable|string',
|
||||
'is_active' => 'boolean',
|
||||
'is_parent' => 'boolean|nullable',
|
||||
'parent_id' => 'nullable|exists:clients,id',
|
||||
'default_tva_rate_id' => 'nullable|exists:tva_rates,id',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'company_id.required' => 'La société est obligatoire.',
|
||||
'company_id.exists' => 'La société sélectionnée n\'existe pas.',
|
||||
'type.required' => 'Le type de client est obligatoire.',
|
||||
'type.in' => 'Le type de client sélectionné est invalide.',
|
||||
'name.required' => 'Le nom du client est obligatoire.',
|
||||
'name.string' => 'Le nom du client doit être une chaîne de caractères.',
|
||||
'name.max' => 'Le nom du client ne peut pas dépasser 255 caractères.',
|
||||
'vat_number.max' => 'Le numéro de TVA ne peut pas dépasser 32 caractères.',
|
||||
'siret.max' => 'Le SIRET ne peut pas dépasser 20 caractères.',
|
||||
'email.email' => 'L\'adresse email doit être valide.',
|
||||
'email.max' => 'L\'adresse email ne peut pas dépasser 191 caractères.',
|
||||
'phone.max' => 'Le téléphone ne peut pas dépasser 50 caractères.',
|
||||
'billing_address_line1.required' => 'L\'adresse facturation est obligatoire.',
|
||||
'billing_address_line1.max' => 'L\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'billing_address_line2.max' => 'Le complément d\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'billing_postal_code.max' => 'Le code postal ne peut pas dépasser 20 caractères.',
|
||||
'billing_city.max' => 'La ville ne peut pas dépasser 191 caractères.',
|
||||
'billing_country_code.size' => 'Le code pays doit contenir 2 caractères.',
|
||||
'group_id.exists' => 'Le groupe de clients sélectionné n\'existe pas.',
|
||||
'is_active.boolean' => 'Le statut actif doit être vrai ou faux.',
|
||||
'default_tva_rate_id.exists' => 'Le taux de TVA sélectionné n\'existe pas.',
|
||||
];
|
||||
}
|
||||
}
|
||||
70
thanasoft-back/app/Http/Requests/StoreContactRequest.php
Normal file
70
thanasoft-back/app/Http/Requests/StoreContactRequest.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreContactRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'client_id' => 'nullable|exists:clients,id',
|
||||
'fournisseur_id' => 'nullable|exists:fournisseurs,id',
|
||||
'first_name' => 'nullable|string|max:191',
|
||||
'last_name' => 'nullable|string|max:191',
|
||||
'email' => 'nullable|email|max:191',
|
||||
'phone' => 'nullable|string|max:50',
|
||||
'role' => 'nullable|string|max:191',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'client_id.exists' => 'Le client sélectionné n\'existe pas.',
|
||||
'fournisseur_id.exists' => 'Le fournisseur sélectionné n\'existe pas.',
|
||||
'first_name.string' => 'Le prénom doit être une chaîne de caractères.',
|
||||
'first_name.max' => 'Le prénom ne peut pas dépasser 191 caractères.',
|
||||
'last_name.string' => 'Le nom doit être une chaîne de caractères.',
|
||||
'last_name.max' => 'Le nom ne peut pas dépasser 191 caractères.',
|
||||
'email.email' => 'L\'adresse email doit être valide.',
|
||||
'email.max' => 'L\'adresse email ne peut pas dépasser 191 caractères.',
|
||||
'phone.max' => 'Le téléphone ne peut pas dépasser 50 caractères.',
|
||||
'role.max' => 'Le rôle ne peut pas dépasser 191 caractères.',
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator($validator)
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
// At least one of client_id or fournisseur_id must be provided
|
||||
if (empty($this->client_id) && empty($this->fournisseur_id)) {
|
||||
$validator->errors()->add(
|
||||
'general',
|
||||
'Le contact doit être associé à un client ou un fournisseur.'
|
||||
);
|
||||
}
|
||||
|
||||
if (empty($this->first_name) && empty($this->last_name) && empty($this->email) && empty($this->phone)) {
|
||||
$validator->errors()->add(
|
||||
'general',
|
||||
'Au moins un champ (prénom, nom, email ou téléphone) doit être renseigné.'
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
41
thanasoft-back/app/Http/Requests/StoreConvoyRequest.php
Normal file
41
thanasoft-back/app/Http/Requests/StoreConvoyRequest.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class StoreConvoyRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'deceased_id' => ['required', 'exists:deceased,id'],
|
||||
'client_id' => ['nullable', 'exists:clients,id'],
|
||||
'vehicle_id' => ['nullable', 'exists:vehicles,id'],
|
||||
'mission_title' => ['nullable', 'string', 'max:255'],
|
||||
'convoy_type' => ['nullable', Rule::in(['local', 'national', 'international'])],
|
||||
'transport_mode' => ['nullable', Rule::in(['road', 'air', 'sea', 'rail'])],
|
||||
'status' => ['nullable', Rule::in(['planned', 'in_progress', 'completed', 'cancelled'])],
|
||||
'planned_start_at' => ['required', 'date'],
|
||||
'estimated_end_at' => ['nullable', 'date', 'after_or_equal:planned_start_at'],
|
||||
'family_email' => ['nullable', 'email', 'max:255'],
|
||||
'automatic_notifications' => ['nullable', 'boolean'],
|
||||
'departure_location_selection_mode' => ['nullable', Rule::in(['place', 'manual'])],
|
||||
'departure_location_id' => ['nullable', 'exists:client_locations,id'],
|
||||
'departure_name' => ['nullable', 'string', 'max:255'],
|
||||
'departure_address' => ['nullable', 'string', 'max:255'],
|
||||
'departure_city' => ['nullable', 'string', 'max:255'],
|
||||
'departure_postal_code' => ['nullable', 'string', 'max:20'],
|
||||
'departure_country_code' => ['nullable', 'string', 'size:2'],
|
||||
'departure_latitude' => ['nullable', 'numeric', 'between:-90,90'],
|
||||
'departure_longitude' => ['nullable', 'numeric', 'between:-180,180'],
|
||||
'departure_additional_details' => ['nullable', 'string'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreDeceasedDocumentRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Add your authorization logic here
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'deceased_id' => 'required|exists:deceased,id',
|
||||
'doc_type' => 'required|string|max:191',
|
||||
'file_id' => 'nullable|exists:files,id',
|
||||
'generated_at' => 'nullable|date',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'deceased_id.required' => 'Le défunt est obligatoire.',
|
||||
'deceased_id.exists' => 'Le défunt sélectionné n\'existe pas.',
|
||||
'doc_type.required' => 'Le type de document est obligatoire.',
|
||||
'doc_type.string' => 'Le type de document doit être une chaîne de caractères.',
|
||||
'doc_type.max' => 'Le type de document ne peut pas dépasser :max caractères.',
|
||||
'file_id.exists' => 'Le fichier sélectionné n\'existe pas.',
|
||||
'generated_at.date' => 'La date de génération doit être une date valide.',
|
||||
];
|
||||
}
|
||||
}
|
||||
49
thanasoft-back/app/Http/Requests/StoreDeceasedRequest.php
Normal file
49
thanasoft-back/app/Http/Requests/StoreDeceasedRequest.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class StoreDeceasedRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
// Add authorization logic if needed
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'last_name' => ['required', 'string', 'max:191'],
|
||||
'first_name' => ['nullable', 'string', 'max:191'],
|
||||
'birth_date' => ['nullable', 'date'],
|
||||
'death_date' => ['nullable', 'date', 'after_or_equal:birth_date'],
|
||||
'place_of_death' => ['nullable', 'string', 'max:255'],
|
||||
'notes' => ['nullable', 'string']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom error messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'last_name.required' => 'Le nom de famille est obligatoire.',
|
||||
'last_name.max' => 'Le nom de famille ne peut pas dépasser 191 caractères.',
|
||||
'first_name.max' => 'Le prénom ne peut pas dépasser 191 caractères.',
|
||||
'death_date.after_or_equal' => 'La date de décès doit être postérieure ou égale à la date de naissance.',
|
||||
'place_of_death.max' => 'Le lieu de décès ne peut pas dépasser 255 caractères.'
|
||||
];
|
||||
}
|
||||
}
|
||||
59
thanasoft-back/app/Http/Requests/StoreEmployeeRequest.php
Normal file
59
thanasoft-back/app/Http/Requests/StoreEmployeeRequest.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreEmployeeRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Add your authorization logic here
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'first_name' => 'required|string|max:191',
|
||||
'last_name' => 'required|string|max:191',
|
||||
'email' => 'nullable|email|max:191|unique:employees,email',
|
||||
'phone' => 'nullable|string|max:50',
|
||||
'job_title' => 'nullable|string|max:191',
|
||||
'hire_date' => 'nullable|date',
|
||||
'active' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'first_name.required' => 'Le prénom est obligatoire.',
|
||||
'first_name.string' => 'Le prénom doit être une chaîne de caractères.',
|
||||
'first_name.max' => 'Le prénom ne peut pas dépasser :max caractères.',
|
||||
'last_name.required' => 'Le nom de famille est obligatoire.',
|
||||
'last_name.string' => 'Le nom de famille doit être une chaîne de caractères.',
|
||||
'last_name.max' => 'Le nom de famille ne peut pas dépasser :max caractères.',
|
||||
'email.email' => 'L\'adresse email doit être valide.',
|
||||
'email.unique' => 'Cette adresse email est déjà utilisée.',
|
||||
'phone.string' => 'Le téléphone doit être une chaîne de caractères.',
|
||||
'phone.max' => 'Le téléphone ne peut pas dépasser :max caractères.',
|
||||
'job_title.string' => 'L\'intitulé du poste doit être une chaîne de caractères.',
|
||||
'job_title.max' => 'L\'intitulé du poste ne peut pas dépasser :max caractères.',
|
||||
'hire_date.date' => 'La date d\'embauche doit être une date valide.',
|
||||
'active.boolean' => 'Le statut actif doit être un booléen.',
|
||||
];
|
||||
}
|
||||
}
|
||||
109
thanasoft-back/app/Http/Requests/StoreFileRequest.php
Normal file
109
thanasoft-back/app/Http/Requests/StoreFileRequest.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class StoreFileRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return Auth::check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'file' => 'required|file|max:10240', // Max 10MB
|
||||
'file_name' => 'nullable|string|max:255',
|
||||
'category' => 'required|string|in:devis,facture,contrat,document,image,autre',
|
||||
'client_id' => 'nullable|integer|exists:clients,id',
|
||||
'subcategory' => 'nullable|string|max:100',
|
||||
'description' => 'nullable|string|max:500',
|
||||
'tags' => 'nullable|array|max:10',
|
||||
'tags.*' => 'string|max:50',
|
||||
'is_public' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attributes for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'file' => 'fichier',
|
||||
'file_name' => 'nom du fichier',
|
||||
'category' => 'catégorie',
|
||||
'client_id' => 'client',
|
||||
'subcategory' => 'sous-catégorie',
|
||||
'description' => 'description',
|
||||
'tags' => 'étiquettes',
|
||||
'is_public' => 'visibilité publique',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'file.required' => 'Le fichier est obligatoire.',
|
||||
'file.file' => 'Le fichier doit être un fichier valide.',
|
||||
'file.max' => 'Le fichier ne peut pas dépasser 10 MB.',
|
||||
'file_name.string' => 'Le nom du fichier doit être une chaîne de caractères.',
|
||||
'file_name.max' => 'Le nom du fichier ne peut pas dépasser 255 caractères.',
|
||||
'category.required' => 'La catégorie est obligatoire.',
|
||||
'category.in' => 'La catégorie sélectionnée n\'est pas valide.',
|
||||
'client_id.exists' => 'Le client sélectionné n\'existe pas.',
|
||||
'subcategory.string' => 'La sous-catégorie doit être une chaîne de caractères.',
|
||||
'subcategory.max' => 'La sous-catégorie ne peut pas dépasser 100 caractères.',
|
||||
'description.string' => 'La description doit être une chaîne de caractères.',
|
||||
'description.max' => 'La description ne peut pas dépasser 500 caractères.',
|
||||
'tags.array' => 'Les étiquettes doivent être un tableau.',
|
||||
'tags.max' => 'Vous ne pouvez pas ajouter plus de 10 étiquettes.',
|
||||
'tags.*.string' => 'Chaque étiquette doit être une chaîne de caractères.',
|
||||
'tags.*.max' => 'Chaque étiquette ne peut pas dépasser 50 caractères.',
|
||||
'is_public.boolean' => 'La visibilité publique doit être vrai ou faux.',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
// Set default values
|
||||
$this->merge([
|
||||
'uploaded_by' => $this->user()->id,
|
||||
'is_public' => $this->boolean('is_public', false),
|
||||
'category' => $this->input('category', 'autre'), // Default category to 'autre' if not provided
|
||||
]);
|
||||
|
||||
// If no file_name provided, use the original file name
|
||||
if (!$this->has('file_name') && $this->hasFile('file')) {
|
||||
$this->merge([
|
||||
'file_name' => $this->file->getClientOriginalName(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Extract file information
|
||||
if ($this->hasFile('file')) {
|
||||
$file = $this->file;
|
||||
$this->merge([
|
||||
'mime_type' => $file->getMimeType(),
|
||||
'size_bytes' => $file->getSize(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
59
thanasoft-back/app/Http/Requests/StoreFournisseurRequest.php
Normal file
59
thanasoft-back/app/Http/Requests/StoreFournisseurRequest.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreFournisseurRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|max:255',
|
||||
'vat_number' => 'nullable|string|max:32',
|
||||
'siret' => 'nullable|string|max:20',
|
||||
'email' => 'nullable|email|max:191',
|
||||
'phone' => 'nullable|string|max:50',
|
||||
'billing_address_line1' => 'nullable|string|max:255',
|
||||
'billing_address_line2' => 'nullable|string|max:255',
|
||||
'billing_postal_code' => 'nullable|string|max:20',
|
||||
'billing_city' => 'nullable|string|max:191',
|
||||
'billing_country_code' => 'nullable|string|size:2',
|
||||
'notes' => 'nullable|string',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'name.required' => 'Le nom du fournisseur est obligatoire.',
|
||||
'name.string' => 'Le nom du fournisseur doit être une chaîne de caractères.',
|
||||
'name.max' => 'Le nom du fournisseur ne peut pas dépasser 255 caractères.',
|
||||
'vat_number.max' => 'Le numéro de TVA ne peut pas dépasser 32 caractères.',
|
||||
'siret.max' => 'Le SIRET ne peut pas dépasser 20 caractères.',
|
||||
'email.email' => 'L\'adresse email doit être valide.',
|
||||
'email.max' => 'L\'adresse email ne peut pas dépasser 191 caractères.',
|
||||
'phone.max' => 'Le téléphone ne peut pas dépasser 50 caractères.',
|
||||
'billing_address_line1.max' => 'L\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'billing_address_line2.max' => 'Le complément d\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'billing_postal_code.max' => 'Le code postal ne peut pas dépasser 20 caractères.',
|
||||
'billing_city.max' => 'La ville ne peut pas dépasser 191 caractères.',
|
||||
'billing_country_code.size' => 'Le code pays doit contenir 2 caractères.',
|
||||
'is_active.boolean' => 'Le statut actif doit être vrai ou faux.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreGoodsReceiptRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'purchase_order_id' => 'required|exists:purchase_orders,id',
|
||||
'warehouse_id' => 'required|exists:warehouses,id',
|
||||
'receipt_number' => 'required|string|max:191',
|
||||
'receipt_date' => 'required|date',
|
||||
'status' => 'nullable|in:draft,posted',
|
||||
'notes' => 'nullable|string',
|
||||
'lines' => 'nullable|array',
|
||||
'lines.*.product_id' => 'required_with:lines|exists:products,id',
|
||||
'lines.*.packaging_id' => 'nullable|exists:product_packagings,id',
|
||||
'lines.*.packages_qty_received' => 'nullable|numeric|min:0',
|
||||
'lines.*.units_qty_received' => 'nullable|numeric|min:0',
|
||||
'lines.*.qty_received_base' => 'nullable|numeric|min:0',
|
||||
'lines.*.unit_price' => 'nullable|numeric|min:0',
|
||||
'lines.*.unit_price_per_package' => 'nullable|numeric|min:0',
|
||||
'lines.*.tva_rate_id' => 'nullable|exists:tva_rates,id',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'purchase_order_id.required' => 'La commande fournisseur est requise.',
|
||||
'purchase_order_id.exists' => 'La commande fournisseur spécifiée n\'existe pas.',
|
||||
'warehouse_id.required' => 'L\'entrepôt est requis.',
|
||||
'warehouse_id.exists' => 'L\'entrepôt spécifié n\'existe pas.',
|
||||
'receipt_number.required' => 'Le numéro de réception est requis.',
|
||||
'receipt_number.string' => 'Le numéro de réception doit être une chaîne de caractères.',
|
||||
'receipt_number.max' => 'Le numéro de réception ne peut pas dépasser 191 caractères.',
|
||||
'receipt_date.required' => 'La date de réception est requise.',
|
||||
'receipt_date.date' => 'La date de réception doit être une date valide.',
|
||||
'status.in' => 'Le statut doit être "draft" ou "posted".',
|
||||
'notes.string' => 'Les notes doivent être une chaîne de caractères.',
|
||||
'lines.array' => 'Les lignes doivent être un tableau.',
|
||||
'lines.*.product_id.required_with' => 'Le produit est requis pour chaque ligne.',
|
||||
'lines.*.product_id.exists' => 'Le produit spécifié dans une ligne n\'existe pas.',
|
||||
'lines.*.packaging_id.exists' => 'Le conditionnement spécifié dans une ligne n\'existe pas.',
|
||||
'lines.*.packages_qty_received.numeric' => 'La quantité de colis doit être un nombre.',
|
||||
'lines.*.packages_qty_received.min' => 'La quantité de colis ne peut pas être négative.',
|
||||
'lines.*.units_qty_received.numeric' => 'La quantité d\'unités doit être un nombre.',
|
||||
'lines.*.units_qty_received.min' => 'La quantité d\'unités ne peut pas être négative.',
|
||||
'lines.*.qty_received_base.numeric' => 'La quantité de base doit être un nombre.',
|
||||
'lines.*.qty_received_base.min' => 'La quantité de base ne peut pas être négative.',
|
||||
'lines.*.unit_price.numeric' => 'Le prix unitaire doit être un nombre.',
|
||||
'lines.*.unit_price.min' => 'Le prix unitaire ne peut pas être négatif.',
|
||||
'lines.*.unit_price_per_package.numeric' => 'Le prix par colis doit être un nombre.',
|
||||
'lines.*.unit_price_per_package.min' => 'Le prix par colis ne peut pas être négatif.',
|
||||
'lines.*.tva_rate_id.exists' => 'Le taux de TVA spécifié dans une ligne n\'existe pas.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class StoreInterventionRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
// Add authorization logic if needed
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'client_id' => ['required', 'exists:clients,id'],
|
||||
'deceased_id' => ['nullable', 'exists:deceased,id'],
|
||||
'order_giver' => ['nullable', 'string', 'max:255'],
|
||||
'location_id' => ['nullable', 'exists:client_locations,id'],
|
||||
'product_id' => ['nullable', 'exists:products,id'],
|
||||
'type' => ['required', Rule::in([
|
||||
'thanatopraxie',
|
||||
'toilette_mortuaire',
|
||||
'exhumation',
|
||||
'retrait_pacemaker',
|
||||
'retrait_bijoux',
|
||||
'autre'
|
||||
])],
|
||||
'scheduled_at' => ['nullable', 'date_format:Y-m-d H:i:s'],
|
||||
'duration_min' => ['nullable', 'integer', 'min:0'],
|
||||
'status' => ['sometimes', Rule::in([
|
||||
'demande',
|
||||
'planifie',
|
||||
'en_cours',
|
||||
'termine',
|
||||
'annule'
|
||||
])],
|
||||
'practitioners' => ['nullable', 'array'],
|
||||
'practitioners.*' => ['exists:thanatopractitioners,id'],
|
||||
'principal_practitioner_id' => ['nullable', 'exists:thanatopractitioners,id'],
|
||||
'assistant_practitioner_ids' => ['nullable', 'array'],
|
||||
'assistant_practitioner_ids.*' => ['exists:thanatopractitioners,id'],
|
||||
'notes' => ['nullable', 'string'],
|
||||
'created_by' => ['nullable', 'exists:users,id']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom error messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'client_id.required' => 'Le client est obligatoire.',
|
||||
'client_id.exists' => 'Le client sélectionné est invalide.',
|
||||
'deceased_id.exists' => 'Le défunt sélectionné est invalide.',
|
||||
'order_giver.max' => 'Le donneur d\'ordre ne peut pas dépasser 255 caractères.',
|
||||
'location_id.exists' => 'Le lieu sélectionné est invalide.',
|
||||
'type.required' => 'Le type d\'intervention est obligatoire.',
|
||||
'type.in' => 'Le type d\'intervention est invalide.',
|
||||
'scheduled_at.date_format' => 'Le format de la date programmée est invalide.',
|
||||
'duration_min.integer' => 'La durée doit être un nombre entier.',
|
||||
'duration_min.min' => 'La durée ne peut pas être négative.',
|
||||
'status.in' => 'Le statut de l\'intervention est invalide.',
|
||||
'practitioners.array' => 'Les praticiens doivent être un tableau.',
|
||||
'practitioners.*.exists' => 'Un des praticiens sélectionnés est invalide.',
|
||||
'principal_practitioner_id.exists' => 'Le praticien principal sélectionné est invalide.',
|
||||
'assistant_practitioner_ids.array' => 'Les praticiens assistants doivent être un tableau.',
|
||||
'assistant_practitioner_ids.*.exists' => 'Un des praticiens assistants est invalide.',
|
||||
'created_by.exists' => 'L\'utilisateur créateur est invalide.'
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class StoreInterventionWithAllDataRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'deceased_id' => ['nullable', 'exists:deceased,id'],
|
||||
'deceased' => ['required_without:deceased_id', 'array'],
|
||||
'deceased.last_name' => ['required_without:deceased_id', 'string', 'max:191'],
|
||||
'deceased.first_name' => ['nullable', 'string', 'max:191'],
|
||||
'deceased.birth_date' => ['nullable', 'date'],
|
||||
'deceased.death_date' => ['nullable', 'date', 'after_or_equal:deceased.birth_date'],
|
||||
'deceased.place_of_death' => ['nullable', 'string', 'max:255'],
|
||||
'deceased.notes' => ['nullable', 'string'],
|
||||
|
||||
'client_id' => ['nullable', 'exists:clients,id'],
|
||||
'client' => 'required_without:client_id|array',
|
||||
'client.name' => ['required', 'string', 'max:255'],
|
||||
'client.vat_number' => ['nullable', 'string', 'max:32'],
|
||||
'client.siret' => ['nullable', 'string', 'max:20'],
|
||||
'client.email' => ['nullable', 'email', 'max:191'],
|
||||
'client.phone' => ['nullable', 'string', 'max:50'],
|
||||
'client.billing_address_line1' => ['nullable', 'string', 'max:255'],
|
||||
'client.billing_address_line2' => ['nullable', 'string', 'max:255'],
|
||||
'client.billing_postal_code' => ['nullable', 'string', 'max:20'],
|
||||
'client.billing_city' => ['nullable', 'string', 'max:191'],
|
||||
'client.billing_country_code' => ['nullable', 'string', 'size:2'],
|
||||
'client.notes' => ['nullable', 'string'],
|
||||
|
||||
'contact' => 'nullable|array',
|
||||
'contact.first_name' => ['nullable', 'string', 'max:191'],
|
||||
'contact.last_name' => ['nullable', 'string', 'max:191'],
|
||||
'contact.email' => ['nullable', 'email', 'max:191'],
|
||||
'contact.phone' => ['nullable', 'string', 'max:50'],
|
||||
'contact.role' => ['nullable', 'string', 'max:191'],
|
||||
|
||||
'location_id' => ['nullable', 'exists:client_locations,id'],
|
||||
'location' => ['nullable', 'array'],
|
||||
'location.name' => ['required_without:location_id', 'string', 'max:255'],
|
||||
'location.address' => ['nullable', 'string', 'max:255'],
|
||||
'location.city' => ['required_without:location_id', 'string', 'max:191'],
|
||||
'location.postal_code' => ['nullable', 'string', 'max:20'],
|
||||
'location.country_code' => ['nullable', 'string', 'size:2'],
|
||||
'location.access_instructions' => ['nullable', 'string'],
|
||||
'location.notes' => ['nullable', 'string'],
|
||||
|
||||
'documents' => 'nullable|array',
|
||||
'documents.*.file' => ['required', 'file'],
|
||||
'documents.*.name' => ['required', 'string', 'max:255'],
|
||||
'documents.*.description' => ['nullable', 'string'],
|
||||
|
||||
'intervention' => 'required|array',
|
||||
'intervention.type' => [
|
||||
'required',
|
||||
Rule::in([
|
||||
'thanatopraxie',
|
||||
'toilette_mortuaire',
|
||||
'exhumation',
|
||||
'retrait_pacemaker',
|
||||
'retrait_bijoux',
|
||||
'autre'
|
||||
])
|
||||
],
|
||||
'intervention.scheduled_at' => ['nullable', 'date_format:Y-m-d H:i:s'],
|
||||
'intervention.duration_min' => ['nullable', 'integer', 'min:0'],
|
||||
'intervention.status' => [
|
||||
'sometimes',
|
||||
Rule::in([
|
||||
'demande',
|
||||
'planifie',
|
||||
'en_cours',
|
||||
'termine',
|
||||
'annule'
|
||||
])
|
||||
],
|
||||
'intervention.practitioners' => ['nullable', 'array'],
|
||||
'intervention.practitioners.*' => ['exists:thanatopractitioners,id'],
|
||||
'intervention.principal_practitioner_id' => ['nullable', 'exists:thanatopractitioners,id'],
|
||||
'intervention.assistant_practitioner_ids' => ['nullable', 'array'],
|
||||
'intervention.assistant_practitioner_ids.*' => ['exists:thanatopractitioners,id'],
|
||||
'intervention.order_giver' => ['nullable', 'string', 'max:255'],
|
||||
'intervention.notes' => ['nullable', 'string'],
|
||||
'intervention.created_by' => ['nullable', 'exists:users,id']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom error messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
$messages = [
|
||||
'deceased.required' => 'Les informations du défunt sont obligatoires.',
|
||||
'deceased.last_name.required' => 'Le nom de famille du défunt est obligatoire.',
|
||||
'deceased.last_name.max' => 'Le nom de famille ne peut pas dépasser 191 caractères.',
|
||||
'deceased.first_name.max' => 'Le prénom ne peut pas dépasser 191 caractères.',
|
||||
'deceased.death_date.after_or_equal' => 'La date de décès doit être postérieure ou égale à la date de naissance.',
|
||||
'deceased.place_of_death.max' => 'Le lieu de décès ne peut pas dépasser 255 caractères.',
|
||||
|
||||
'client.required' => 'Les informations du client sont obligatoires.',
|
||||
'client.name.required' => 'Le nom du client est obligatoire.',
|
||||
'client.name.max' => 'Le nom du client ne peut pas dépasser 255 caractères.',
|
||||
'client.vat_number.max' => 'Le numéro de TVA ne peut pas dépasser 32 caractères.',
|
||||
'client.siret.max' => 'Le SIRET ne peut pas dépasser 20 caractères.',
|
||||
'client.email.email' => 'L\'adresse email doit être valide.',
|
||||
'client.email.max' => 'L\'adresse email ne peut pas dépasser 191 caractères.',
|
||||
'client.phone.max' => 'Le téléphone ne peut pas dépasser 50 caractères.',
|
||||
'client.billing_address_line1.max' => 'L\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'client.billing_postal_code.max' => 'Le code postal ne peut pas dépasser 20 caractères.',
|
||||
'client.billing_city.max' => 'La ville ne peut pas dépasser 191 caractères.',
|
||||
'client.billing_country_code.size' => 'Le code pays doit contenir 2 caractères.',
|
||||
'client.is_active.boolean' => 'Le statut actif doit être vrai ou faux.',
|
||||
|
||||
'contact.first_name.max' => 'Le prénom ne peut pas dépasser 191 caractères.',
|
||||
'contact.last_name.max' => 'Le nom ne peut pas dépasser 191 caractères.',
|
||||
'contact.email.email' => 'L\'adresse email doit être valide.',
|
||||
'contact.email.max' => 'L\'adresse email ne peut pas dépasser 191 caractères.',
|
||||
'contact.phone.max' => 'Le téléphone ne peut pas dépasser 50 caractères.',
|
||||
'contact.role.max' => 'Le rôle ne peut pas dépasser 191 caractères.',
|
||||
|
||||
'intervention.required' => 'Les informations de l\'intervention sont obligatoires.',
|
||||
'intervention.type.required' => 'Le type d\'intervention est obligatoire.',
|
||||
'intervention.type.in' => 'Le type d\'intervention est invalide.',
|
||||
'intervention.scheduled_at.date_format' => 'Le format de la date programmée est invalide.',
|
||||
'intervention.duration_min.integer' => 'La durée doit être un nombre entier.',
|
||||
'intervention.duration_min.min' => 'La durée ne peut pas être négative.',
|
||||
'intervention.status.in' => 'Le statut de l\'intervention est invalide.',
|
||||
'intervention.practitioners.array' => 'Les praticiens doivent être un tableau.',
|
||||
'intervention.practitioners.*.exists' => 'Un des praticiens sélectionnés est invalide.',
|
||||
'intervention.principal_practitioner_id.exists' => 'Le praticien principal sélectionné est invalide.',
|
||||
'intervention.assistant_practitioner_ids.array' => 'Les praticiens assistants doivent être un tableau.',
|
||||
'intervention.assistant_practitioner_ids.*.exists' => 'Un des praticiens assistants est invalide.',
|
||||
'intervention.order_giver.max' => 'Le donneur d\'ordre ne peut pas dépasser 255 caractères.',
|
||||
'intervention.created_by.exists' => 'L\'utilisateur créateur est invalide.'
|
||||
];
|
||||
|
||||
// Add document-specific messages
|
||||
$messages = array_merge($messages, [
|
||||
'documents.array' => 'Les documents doivent être un tableau.',
|
||||
'documents.*.file.required' => 'Chaque document doit avoir un fichier.',
|
||||
'documents.*.name.required' => 'Le nom du document est obligatoire.',
|
||||
'documents.*.name.max' => 'Le nom du document ne peut pas dépasser 255 caractères.',
|
||||
]);
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a failed validation attempt.
|
||||
*/
|
||||
protected function failedValidation(\Illuminate\Contracts\Validation\Validator $validator)
|
||||
{
|
||||
$errors = $validator->errors();
|
||||
|
||||
// Group errors by step for better UX
|
||||
$groupedErrors = [
|
||||
'deceased' => [],
|
||||
'client' => [],
|
||||
'contact' => [],
|
||||
'location' => [],
|
||||
'documents' => [],
|
||||
'intervention' => [],
|
||||
'global' => []
|
||||
];
|
||||
|
||||
foreach ($errors->messages() as $field => $messages) {
|
||||
if (str_starts_with($field, 'deceased.')) {
|
||||
$groupedErrors['deceased'] = array_merge($groupedErrors['deceased'], $messages);
|
||||
} elseif (str_starts_with($field, 'client.')) {
|
||||
$groupedErrors['client'] = array_merge($groupedErrors['client'], $messages);
|
||||
} elseif (str_starts_with($field, 'contact.')) {
|
||||
$groupedErrors['contact'] = array_merge($groupedErrors['contact'], $messages);
|
||||
} elseif (str_starts_with($field, 'location.')) {
|
||||
$groupedErrors['location'] = array_merge($groupedErrors['location'], $messages);
|
||||
} elseif (str_starts_with($field, 'documents.')) {
|
||||
$groupedErrors['documents'] = array_merge($groupedErrors['documents'], $messages);
|
||||
} elseif (str_starts_with($field, 'intervention.')) {
|
||||
$groupedErrors['intervention'] = array_merge($groupedErrors['intervention'], $messages);
|
||||
} else {
|
||||
$groupedErrors['global'] = array_merge($groupedErrors['global'], $messages);
|
||||
}
|
||||
}
|
||||
|
||||
parent::failedValidation($validator);
|
||||
}
|
||||
}
|
||||
99
thanasoft-back/app/Http/Requests/StoreInvoiceRequest.php
Normal file
99
thanasoft-back/app/Http/Requests/StoreInvoiceRequest.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreInvoiceRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'client_id' => 'nullable|exists:clients,id',
|
||||
'group_id' => 'nullable|exists:client_groups,id',
|
||||
'source_quote_id' => 'nullable|exists:quotes,id',
|
||||
'status' => 'required|in:brouillon,emise,envoyee,partiellement_payee,payee,echue,annulee,avoir',
|
||||
'invoice_date' => 'required|date',
|
||||
'due_date' => 'nullable|date|after_or_equal:invoice_date',
|
||||
'currency' => 'required|string|size:3',
|
||||
'total_ht' => 'required|numeric|min:0',
|
||||
'total_tva' => 'required|numeric|min:0',
|
||||
'total_ttc' => 'required|numeric|min:0',
|
||||
'e_invoicing_channel_id' => 'nullable|exists:e_invoicing_channels,id',
|
||||
'e_invoice_status' => 'nullable|in:cree,transmis,accepte,refuse,en_litige,acquitte,archive',
|
||||
'lines' => 'required|array|min:1',
|
||||
'lines.*.product_id' => 'nullable|exists:products,id',
|
||||
'lines.*.packaging_id' => 'nullable|exists:product_packagings,id',
|
||||
'lines.*.packages_qty' => 'nullable|numeric|min:0',
|
||||
'lines.*.units_qty' => 'nullable|numeric|min:0',
|
||||
'lines.*.description' => 'required|string',
|
||||
'lines.*.qty_base' => 'nullable|numeric|min:0',
|
||||
'lines.*.unit_price' => 'required|numeric|min:0',
|
||||
'lines.*.unit_price_per_package' => 'nullable|numeric|min:0',
|
||||
'lines.*.tva_rate_id' => 'nullable|exists:tva_rates,id',
|
||||
'lines.*.discount_pct' => 'required|numeric|min:0|max:100',
|
||||
'lines.*.total_ht' => 'required|numeric|min:0',
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator($validator): void
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
$hasClient = filled($this->input('client_id'));
|
||||
$hasGroup = filled($this->input('group_id'));
|
||||
|
||||
if (! $hasClient && ! $hasGroup) {
|
||||
$message = 'Un client ou un groupe de clients est obligatoire.';
|
||||
|
||||
$validator->errors()->add('client_id', $message);
|
||||
$validator->errors()->add('group_id', $message);
|
||||
}
|
||||
|
||||
if ($hasClient && $hasGroup) {
|
||||
$message = 'Selectionnez soit un client, soit un groupe de clients.';
|
||||
|
||||
$validator->errors()->add('client_id', $message);
|
||||
$validator->errors()->add('group_id', $message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'client_id.exists' => 'Le client sélectionné est invalide.',
|
||||
'group_id.exists' => 'Le groupe sélectionné est invalide.',
|
||||
'status.required' => 'Le statut est obligatoire.',
|
||||
'status.in' => 'Le statut sélectionné est invalide.',
|
||||
'invoice_date.required' => 'La date de la facture est obligatoire.',
|
||||
'invoice_date.date' => 'La date de la facture n\'est pas valide.',
|
||||
'due_date.date' => 'La date d\'échéance n\'est pas valide.',
|
||||
'due_date.after_or_equal' => 'La date d\'échéance doit être postérieure ou égale à la date de la facture.',
|
||||
'currency.required' => 'La devise est obligatoire.',
|
||||
'currency.size' => 'La devise doit comporter 3 caractères.',
|
||||
'total_ht.required' => 'Le total HT est obligatoire.',
|
||||
'total_ht.numeric' => 'Le total HT doit être un nombre.',
|
||||
'total_ht.min' => 'Le total HT ne peut pas être négatif.',
|
||||
'total_tva.required' => 'Le total TVA est obligatoire.',
|
||||
'total_tva.numeric' => 'Le total TVA doit être un nombre.',
|
||||
'total_tva.min' => 'Le total TVA ne peut pas être négatif.',
|
||||
'total_ttc.required' => 'Le total TTC est obligatoire.',
|
||||
'total_ttc.numeric' => 'Le total TTC doit être un nombre.',
|
||||
'total_ttc.min' => 'Le total TTC ne peut pas être négatif.',
|
||||
'lines.required' => 'Veuillez ajouter au moins une ligne à la facture.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StorePractitionerDocumentRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Add your authorization logic here
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'practitioner_id' => 'required|exists:thanatopractitioners,id',
|
||||
'doc_type' => 'required|string|max:191',
|
||||
'file_id' => 'nullable|exists:files,id',
|
||||
'issue_date' => 'nullable|date',
|
||||
'expiry_date' => 'nullable|date|after_or_equal:issue_date',
|
||||
'status' => 'nullable|string|max:64',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'practitioner_id.required' => 'Le thanatopractitioner est obligatoire.',
|
||||
'practitioner_id.exists' => 'Le thanatopractitioner sélectionné n\'existe pas.',
|
||||
'doc_type.required' => 'Le type de document est obligatoire.',
|
||||
'doc_type.string' => 'Le type de document doit être une chaîne de caractères.',
|
||||
'doc_type.max' => 'Le type de document ne peut pas dépasser :max caractères.',
|
||||
'file_id.exists' => 'Le fichier sélectionné n\'existe pas.',
|
||||
'issue_date.date' => 'La date de délivrance doit être une date valide.',
|
||||
'expiry_date.date' => 'La date d\'expiration doit être une date valide.',
|
||||
'expiry_date.after_or_equal' => 'La date d\'expiration doit être égale ou postérieure à la date de délivrance.',
|
||||
'status.string' => 'Le statut doit être une chaîne de caractères.',
|
||||
'status.max' => 'Le statut ne peut pas dépasser :max caractères.',
|
||||
];
|
||||
}
|
||||
}
|
||||
43
thanasoft-back/app/Http/Requests/StorePriceListRequest.php
Normal file
43
thanasoft-back/app/Http/Requests/StorePriceListRequest.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StorePriceListRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|max:191|unique:price_lists,name',
|
||||
'valid_from' => 'nullable|date',
|
||||
'valid_to' => 'nullable|date|after_or_equal:valid_from',
|
||||
'is_default' => 'nullable|boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'name.required' => 'Le nom de la liste de prix est obligatoire.',
|
||||
'name.unique' => 'Une liste de prix avec ce nom existe déjà.',
|
||||
'valid_from.date' => 'La date de début doit être une date valide.',
|
||||
'valid_to.date' => 'La date de fin doit être une date valide.',
|
||||
'valid_to.after_or_equal' => 'La date de fin doit être postérieure ou égale à la date de début.',
|
||||
'is_default.boolean' => 'Le statut par défaut doit être vrai ou faux.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreProductCategoryRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'code' => 'required|string|max:64|unique:product_categories,code',
|
||||
'name' => 'required|string|max:191',
|
||||
'description' => 'nullable|string',
|
||||
'parent_id' => 'nullable|exists:product_categories,id',
|
||||
'intervention' => 'boolean',
|
||||
'active' => 'boolean',
|
||||
];
|
||||
}
|
||||
}
|
||||
89
thanasoft-back/app/Http/Requests/StoreProductRequest.php
Normal file
89
thanasoft-back/app/Http/Requests/StoreProductRequest.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreProductRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'nom' => 'required|string|max:255',
|
||||
'reference' => 'required|string|max:100|unique:products,reference',
|
||||
'categorie_id' => 'required|exists:product_categories,id',
|
||||
'fabricant' => 'nullable|string|max:191',
|
||||
'stock_actuel' => 'required|numeric|min:0',
|
||||
'stock_minimum' => 'required|numeric|min:0',
|
||||
'unite' => 'required|string|max:50',
|
||||
'prix_unitaire' => 'required|numeric|min:0',
|
||||
'date_expiration' => 'nullable|date|after:today',
|
||||
'numero_lot' => 'nullable|string|max:100',
|
||||
'conditionnement_nom' => 'nullable|string|max:191',
|
||||
'conditionnement_quantite' => 'nullable|numeric|min:0',
|
||||
'conditionnement_unite' => 'nullable|string|max:50',
|
||||
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
|
||||
'remove_image' => 'nullable|boolean',
|
||||
'fiche_technique_url' => 'nullable|url|max:500',
|
||||
'fournisseur_id' => 'nullable|exists:fournisseurs,id',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'nom.required' => 'Le nom du produit est obligatoire.',
|
||||
'nom.string' => 'Le nom du produit doit être une chaîne de caractères.',
|
||||
'nom.max' => 'Le nom du produit ne peut pas dépasser 255 caractères.',
|
||||
'reference.required' => 'La référence du produit est obligatoire.',
|
||||
'reference.string' => 'La référence du produit doit être une chaîne de caractères.',
|
||||
'reference.max' => 'La référence du produit ne peut pas dépasser 100 caractères.',
|
||||
'reference.unique' => 'Cette référence de produit existe déjà.',
|
||||
'categorie_id.required' => 'La catégorie est obligatoire.',
|
||||
'categorie_id.exists' => 'La catégorie sélectionnée n\'existe pas.',
|
||||
'fabricant.string' => 'Le fabricant doit être une chaîne de caractères.',
|
||||
'fabricant.max' => 'Le fabricant ne peut pas dépasser 191 caractères.',
|
||||
'stock_actuel.required' => 'Le stock actuel est obligatoire.',
|
||||
'stock_actuel.numeric' => 'Le stock actuel doit être un nombre.',
|
||||
'stock_actuel.min' => 'Le stock actuel doit être supérieur ou égal à 0.',
|
||||
'stock_minimum.required' => 'Le stock minimum est obligatoire.',
|
||||
'stock_minimum.numeric' => 'Le stock minimum doit être un nombre.',
|
||||
'stock_minimum.min' => 'Le stock minimum doit être supérieur ou égal à 0.',
|
||||
'unite.required' => 'L\'unité est obligatoire.',
|
||||
'unite.string' => 'L\'unité doit être une chaîne de caractères.',
|
||||
'unite.max' => 'L\'unité ne peut pas dépasser 50 caractères.',
|
||||
'prix_unitaire.required' => 'Le prix unitaire est obligatoire.',
|
||||
'prix_unitaire.numeric' => 'Le prix unitaire doit être un nombre.',
|
||||
'prix_unitaire.min' => 'Le prix unitaire doit être supérieur ou égal à 0.',
|
||||
'date_expiration.date' => 'La date d\'expiration doit être une date valide.',
|
||||
'date_expiration.after' => 'La date d\'expiration doit être postérieure à aujourd\'hui.',
|
||||
'numero_lot.string' => 'Le numéro de lot doit être une chaîne de caractères.',
|
||||
'numero_lot.max' => 'Le numéro de lot ne peut pas dépasser 100 caractères.',
|
||||
'conditionnement_nom.string' => 'Le nom du conditionnement doit être une chaîne de caractères.',
|
||||
'conditionnement_nom.max' => 'Le nom du conditionnement ne peut pas dépasser 191 caractères.',
|
||||
'conditionnement_quantite.numeric' => 'La quantité du conditionnement doit être un nombre.',
|
||||
'conditionnement_quantite.min' => 'La quantité du conditionnement doit être supérieure ou égal à 0.',
|
||||
'conditionnement_unite.string' => 'L\'unité du conditionnement doit être une chaîne de caractères.',
|
||||
'conditionnement_unite.max' => 'L\'unité du conditionnement ne peut pas dépasser 50 caractères.',
|
||||
'image.image' => 'Le fichier doit être une image valide.',
|
||||
'image.mimes' => 'L\'image doit être de type: jpeg, png, jpg, gif ou svg.',
|
||||
'image.max' => 'L\'image ne peut pas dépasser 2MB.',
|
||||
'fiche_technique_url.url' => 'L\'URL de la fiche technique doit être une URL valide.',
|
||||
'fiche_technique_url.max' => 'L\'URL de la fiche technique ne peut pas dépasser 500 caractères.',
|
||||
'fournisseur_id.exists' => 'Le fournisseur sélectionné n\'existe pas.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StorePurchaseOrderRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'fournisseur_id' => ['required', 'exists:fournisseurs,id'],
|
||||
'po_number' => ['nullable', 'string', 'max:191', 'unique:purchase_orders,po_number'],
|
||||
'status' => ['nullable', 'in:brouillon,confirmee,livree,facturee,annulee'],
|
||||
'order_date' => ['nullable', 'date'],
|
||||
'expected_date' => ['nullable', 'date'],
|
||||
'currency' => ['nullable', 'string', 'size:3'],
|
||||
'total_ht' => ['required', 'numeric', 'min:0'],
|
||||
'total_tva' => ['required', 'numeric', 'min:0'],
|
||||
'total_ttc' => ['required', 'numeric', 'min:0'],
|
||||
'notes' => ['nullable', 'string'],
|
||||
'delivery_address' => ['nullable', 'string'],
|
||||
'lines' => ['required', 'array', 'min:1'],
|
||||
'lines.*.product_id' => ['nullable', 'exists:products,id'],
|
||||
'lines.*.description' => ['required', 'string'],
|
||||
'lines.*.quantity' => ['required', 'numeric', 'min:0.001'],
|
||||
'lines.*.unit_price' => ['required', 'numeric', 'min:0'],
|
||||
'lines.*.tva_rate' => ['required', 'numeric', 'min:0'],
|
||||
'lines.*.discount_pct' => ['nullable', 'numeric', 'min:0', 'max:100'],
|
||||
'lines.*.total_ht' => ['required', 'numeric', 'min:0'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'fournisseur_id.required' => 'Le fournisseur est obligatoire.',
|
||||
'fournisseur_id.exists' => 'Le fournisseur sélectionné est invalide.',
|
||||
'po_number.unique' => 'Ce numéro de commande existe déjà.',
|
||||
'order_date.date' => 'La date de commande n\'est pas valide.',
|
||||
'expected_date.date' => 'La date de livraison prévue n\'est pas valide.',
|
||||
'status.in' => 'Le statut sélectionné est invalide.',
|
||||
'total_ht.required' => 'Le total HT est obligatoire.',
|
||||
'total_tva.required' => 'Le total TVA est obligatoire.',
|
||||
'total_ttc.required' => 'Le total TTC est obligatoire.',
|
||||
'lines.required' => 'Au moins une ligne d\'article est requise.',
|
||||
'lines.array' => 'Les lignes doivent être un tableau.',
|
||||
'lines.min' => 'Vous devez ajouter au moins une ligne d\'article.',
|
||||
'lines.*.description.required' => 'La désignation est obligatoire pour toutes les lignes.',
|
||||
'lines.*.quantity.required' => 'La quantité est obligatoire.',
|
||||
'lines.*.quantity.numeric' => 'La quantité doit être un nombre.',
|
||||
'lines.*.quantity.min' => 'La quantité doit être supérieure à 0.',
|
||||
'lines.*.unit_price.required' => 'Le prix unitaire est obligatoire.',
|
||||
'lines.*.unit_price.numeric' => 'Le prix unitaire doit être un nombre.',
|
||||
'lines.*.unit_price.min' => 'Le prix unitaire doit être positif.',
|
||||
'lines.*.tva_rate.required' => 'Le taux de TVA est obligatoire.',
|
||||
'lines.*.total_ht.required' => 'Le total HT de la ligne est obligatoire.',
|
||||
];
|
||||
}
|
||||
}
|
||||
39
thanasoft-back/app/Http/Requests/StoreQuoteLineRequest.php
Normal file
39
thanasoft-back/app/Http/Requests/StoreQuoteLineRequest.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreQuoteLineRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'quote_id' => 'required|exists:quotes,id',
|
||||
'product_id' => 'nullable|exists:products,id',
|
||||
'packaging_id' => 'nullable|exists:product_packagings,id',
|
||||
'packages_qty' => 'nullable|numeric|min:0',
|
||||
'units_qty' => 'nullable|numeric|min:0',
|
||||
'description' => 'required|string',
|
||||
'qty_base' => 'nullable|numeric|min:0',
|
||||
'unit_price' => 'required|numeric|min:0',
|
||||
'unit_price_per_package' => 'nullable|numeric|min:0',
|
||||
'tva_rate_id' => 'nullable|exists:tva_rates,id',
|
||||
'discount_pct' => 'required|numeric|min:0|max:100',
|
||||
'total_ht' => 'required|numeric|min:0',
|
||||
];
|
||||
}
|
||||
}
|
||||
96
thanasoft-back/app/Http/Requests/StoreQuoteRequest.php
Normal file
96
thanasoft-back/app/Http/Requests/StoreQuoteRequest.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreQuoteRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'client_id' => 'nullable|exists:clients,id',
|
||||
'group_id' => 'nullable|exists:client_groups,id',
|
||||
'status' => 'required|in:brouillon,envoye,accepte,refuse,expire,annule',
|
||||
'quote_date' => 'required|date',
|
||||
'valid_until' => 'nullable|date|after_or_equal:quote_date',
|
||||
'currency' => 'required|string|size:3',
|
||||
'total_ht' => 'required|numeric|min:0',
|
||||
'total_tva' => 'required|numeric|min:0',
|
||||
'total_ttc' => 'required|numeric|min:0',
|
||||
'lines' => 'required|array|min:1',
|
||||
'lines.*.product_id' => 'nullable|exists:products,id',
|
||||
'lines.*.packaging_id' => 'nullable|exists:product_packagings,id',
|
||||
'lines.*.packages_qty' => 'nullable|numeric|min:0',
|
||||
'lines.*.units_qty' => 'nullable|numeric|min:0',
|
||||
'lines.*.description' => 'required|string',
|
||||
'lines.*.qty_base' => 'nullable|numeric|min:0',
|
||||
'lines.*.unit_price' => 'required|numeric|min:0',
|
||||
'lines.*.unit_price_per_package' => 'nullable|numeric|min:0',
|
||||
'lines.*.tva_rate_id' => 'nullable|exists:tva_rates,id',
|
||||
'lines.*.discount_pct' => 'required|numeric|min:0|max:100',
|
||||
'lines.*.total_ht' => 'required|numeric|min:0',
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator($validator): void
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
$hasClient = filled($this->input('client_id'));
|
||||
$hasGroup = filled($this->input('group_id'));
|
||||
|
||||
if (! $hasClient && ! $hasGroup) {
|
||||
$message = 'Un client ou un groupe de clients est obligatoire.';
|
||||
|
||||
$validator->errors()->add('client_id', $message);
|
||||
$validator->errors()->add('group_id', $message);
|
||||
}
|
||||
|
||||
if ($hasClient && $hasGroup) {
|
||||
$message = 'Sélectionnez soit un client, soit un groupe de clients.';
|
||||
|
||||
$validator->errors()->add('client_id', $message);
|
||||
$validator->errors()->add('group_id', $message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'client_id.nullable' => 'Le client sélectionné est invalide.',
|
||||
'client_id.exists' => 'Le client sélectionné est invalide.',
|
||||
'group_id.exists' => 'Le groupe sélectionné est invalide.',
|
||||
'status.required' => 'Le statut est obligatoire.',
|
||||
'status.in' => 'Le statut sélectionné est invalide.',
|
||||
'quote_date.required' => 'La date du devis est obligatoire.',
|
||||
'quote_date.date' => 'La date du devis n\'est pas valide.',
|
||||
'valid_until.date' => 'La date de validité n\'est pas valide.',
|
||||
'valid_until.after_or_equal' => 'La date de validité doit être postérieure ou égale à la date du devis.',
|
||||
'currency.required' => 'La devise est obligatoire.',
|
||||
'currency.size' => 'La devise doit comporter 3 caractères.',
|
||||
'total_ht.required' => 'Le total HT est obligatoire.',
|
||||
'total_ht.numeric' => 'Le total HT doit être un nombre.',
|
||||
'total_ht.min' => 'Le total HT ne peut pas être négatif.',
|
||||
'total_tva.required' => 'Le total TVA est obligatoire.',
|
||||
'total_tva.numeric' => 'Le total TVA doit être un nombre.',
|
||||
'total_tva.min' => 'Le total TVA ne peut pas être négatif.',
|
||||
'total_ttc.required' => 'Le total TTC est obligatoire.',
|
||||
'total_ttc.numeric' => 'Le total TTC doit être un nombre.',
|
||||
'total_ttc.min' => 'Le total TTC ne peut pas être négatif.',
|
||||
];
|
||||
}
|
||||
}
|
||||
48
thanasoft-back/app/Http/Requests/StoreStockItemRequest.php
Normal file
48
thanasoft-back/app/Http/Requests/StoreStockItemRequest.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreStockItemRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'product_id' => 'required|exists:products,id',
|
||||
'warehouse_id' => 'required|exists:warehouses,id',
|
||||
'qty_on_hand_base' => 'nullable|numeric|min:0',
|
||||
'safety_stock_base' => 'nullable|numeric|min:0',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'product_id.required' => 'Le produit est requis.',
|
||||
'product_id.exists' => 'Le produit sélectionné est invalide.',
|
||||
'warehouse_id.required' => 'L\'entrepôt est requis.',
|
||||
'warehouse_id.exists' => 'L\'entrepôt sélectionné est invalide.',
|
||||
'qty_on_hand_base.numeric' => 'La quantité en stock doit être un nombre.',
|
||||
'safety_stock_base.numeric' => 'Le stock de sécurité doit être un nombre.',
|
||||
];
|
||||
}
|
||||
}
|
||||
58
thanasoft-back/app/Http/Requests/StoreStockMoveRequest.php
Normal file
58
thanasoft-back/app/Http/Requests/StoreStockMoveRequest.php
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreStockMoveRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'product_id' => 'required|exists:products,id',
|
||||
'from_warehouse_id' => 'nullable|exists:warehouses,id',
|
||||
'to_warehouse_id' => 'nullable|exists:warehouses,id',
|
||||
'packaging_id' => 'nullable|exists:product_packagings,id',
|
||||
'packages_qty' => 'nullable|numeric|min:0',
|
||||
'units_qty' => 'nullable|numeric|min:0',
|
||||
'qty_base' => 'required|numeric',
|
||||
'move_type' => 'required|string|max:64',
|
||||
'ref_type' => 'nullable|string|max:64',
|
||||
'ref_id' => 'nullable|integer',
|
||||
'moved_at' => 'nullable|date',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'product_id.required' => 'Le produit est requis.',
|
||||
'product_id.exists' => 'Le produit sélectionné est invalide.',
|
||||
'from_warehouse_id.exists' => 'L\'entrepôt de départ est invalide.',
|
||||
'to_warehouse_id.exists' => 'L\'entrepôt d\'arrivée est invalide.',
|
||||
'packaging_id.exists' => 'Le conditionnement sélectionné est invalide.',
|
||||
'qty_base.required' => 'La quantité de base est requise.',
|
||||
'qty_base.numeric' => 'La quantité de base doit être un nombre.',
|
||||
'move_type.required' => 'Le type de mouvement est requis.',
|
||||
'moved_at.date' => 'La date du mouvement n\'est pas valide.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreThanatopractitionerRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Add your authorization logic here
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'employee_id' => 'required|exists:employees,id|unique:thanatopractitioners,employee_id',
|
||||
'diploma_number' => 'nullable|string|max:191',
|
||||
'diploma_date' => 'nullable|date',
|
||||
'authorization_number' => 'nullable|string|max:191',
|
||||
'authorization_issue_date' => 'nullable|date',
|
||||
'authorization_expiry_date' => 'nullable|date|after_or_equal:authorization_issue_date',
|
||||
'notes' => 'nullable|string',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'employee_id.required' => 'L\'employé est obligatoire.',
|
||||
'employee_id.exists' => 'L\'employé sélectionné n\'existe pas.',
|
||||
'employee_id.unique' => 'Cet employé est déjà enregistré comme thanatopractitioner.',
|
||||
'diploma_number.string' => 'Le numéro de diplôme doit être une chaîne de caractères.',
|
||||
'diploma_number.max' => 'Le numéro de diplôme ne peut pas dépasser :max caractères.',
|
||||
'diploma_date.date' => 'La date d\'obtention du diplôme doit être une date valide.',
|
||||
'authorization_number.string' => 'Le numéro d\'autorisation doit être une chaîne de caractères.',
|
||||
'authorization_number.max' => 'Le numéro d\'autorisation ne peut pas dépasser :max caractères.',
|
||||
'authorization_issue_date.date' => 'La date de délivrance de l\'autorisation doit être une date valide.',
|
||||
'authorization_expiry_date.date' => 'La date d\'expiration de l\'autorisation doit être une date valide.',
|
||||
'authorization_expiry_date.after_or_equal' => 'La date d\'expiration doit être égale ou postérieure à la date de délivrance.',
|
||||
'notes.string' => 'Les notes doivent être une chaîne de caractères.',
|
||||
];
|
||||
}
|
||||
}
|
||||
47
thanasoft-back/app/Http/Requests/StoreTvaRateRequest.php
Normal file
47
thanasoft-back/app/Http/Requests/StoreTvaRateRequest.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreTvaRateRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|max:50',
|
||||
'rate' => 'required|numeric|min:0|max:999.99',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'name.required' => 'Le nom du taux de TVA est requis.',
|
||||
'name.string' => 'Le nom doit être une chaîne de caractères.',
|
||||
'name.max' => 'Le nom ne peut pas dépasser 50 caractères.',
|
||||
'rate.required' => 'Le taux de TVA est requis.',
|
||||
'rate.numeric' => 'Le taux doit être un nombre.',
|
||||
'rate.min' => 'Le taux ne peut pas être négatif.',
|
||||
'rate.max' => 'Le taux ne peut pas dépasser 999.99.',
|
||||
];
|
||||
}
|
||||
}
|
||||
32
thanasoft-back/app/Http/Requests/StoreUserRequest.php
Normal file
32
thanasoft-back/app/Http/Requests/StoreUserRequest.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class StoreUserRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<int, string>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email'],
|
||||
'roles' => ['nullable', 'array'],
|
||||
'roles.*' => ['string', 'max:100'],
|
||||
'permissions' => ['nullable', 'array'],
|
||||
'permissions.*' => ['string', 'max:150'],
|
||||
'password' => ['nullable', 'string', Password::min(8)],
|
||||
];
|
||||
}
|
||||
}
|
||||
33
thanasoft-back/app/Http/Requests/StoreVehicleRequest.php
Normal file
33
thanasoft-back/app/Http/Requests/StoreVehicleRequest.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class StoreVehicleRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'photo_file_name' => ['nullable', 'string', 'max:255'],
|
||||
'photo_file_url' => ['nullable', 'string', 'max:2048'],
|
||||
'photo_mime_type' => ['nullable', 'string', 'max:100'],
|
||||
'photo_size' => ['nullable', 'integer', 'min:0'],
|
||||
'brand' => ['required', 'string', 'max:255'],
|
||||
'model' => ['required', 'string', 'max:255'],
|
||||
'registration_number' => ['required', 'string', 'max:255', 'unique:vehicles,registration_number'],
|
||||
'vehicle_type' => ['nullable', Rule::in(['hearse', 'transport_vehicle', 'utility', 'sedan'])],
|
||||
'fuel_type' => ['nullable', Rule::in(['diesel', 'petrol', 'electric', 'hybrid'])],
|
||||
'year' => ['nullable', 'integer', 'min:1900', 'max:2100'],
|
||||
'primary_user_id' => ['nullable', 'exists:employees,id'],
|
||||
'status' => ['nullable', Rule::in(['active', 'maintenance', 'out_of_service'])],
|
||||
'notes' => ['nullable', 'string'],
|
||||
];
|
||||
}
|
||||
}
|
||||
52
thanasoft-back/app/Http/Requests/StoreWarehouseRequest.php
Normal file
52
thanasoft-back/app/Http/Requests/StoreWarehouseRequest.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreWarehouseRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|max:191',
|
||||
'address_line1' => 'nullable|string|max:255',
|
||||
'address_line2' => 'nullable|string|max:255',
|
||||
'postal_code' => 'nullable|string|max:20',
|
||||
'city' => 'nullable|string|max:191',
|
||||
'country_code' => 'nullable|string|size:2',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'name.required' => 'Le nom de l\'entrepôt est requis.',
|
||||
'name.string' => 'Le nom doit être une chaîne de caractères.',
|
||||
'name.max' => 'Le nom ne peut pas dépasser 191 caractères.',
|
||||
'address_line1.max' => 'L\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'address_line2.max' => 'Le complément d\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'postal_code.max' => 'Le code postal ne peut pas dépasser 20 caractères.',
|
||||
'city.max' => 'La ville ne peut pas dépasser 191 caractères.',
|
||||
'country_code.size' => 'Le code pays doit comporter exactement 2 caractères.',
|
||||
];
|
||||
}
|
||||
}
|
||||
27
thanasoft-back/app/Http/Requests/UpdateAvoirRequest.php
Normal file
27
thanasoft-back/app/Http/Requests/UpdateAvoirRequest.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateAvoirRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'status' => 'sometimes|required|in:brouillon,emis,applique,annule',
|
||||
'avoir_date' => 'sometimes|required|date',
|
||||
'due_date' => 'nullable|date|after_or_equal:avoir_date',
|
||||
'refund_status' => 'nullable|in:non_rembourse,en_cours,partiellement_rembourse,rembourse,compense',
|
||||
'refund_date' => 'nullable|date',
|
||||
'refund_method' => 'nullable|in:virement,cheque,carte_credit,compensation_future,autre',
|
||||
'reason_description' => 'nullable|string',
|
||||
'notes' => 'nullable|string',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateClientGroupRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$routeClientGroup = $this->route('client_group');
|
||||
$clientGroupId = is_object($routeClientGroup)
|
||||
? $routeClientGroup->id
|
||||
: ($routeClientGroup ?? $this->route('id'));
|
||||
|
||||
return [
|
||||
'name' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:191',
|
||||
Rule::unique('client_groups', 'name')->ignore($clientGroupId)
|
||||
],
|
||||
'description' => 'nullable|string',
|
||||
'client_ids' => 'sometimes|array',
|
||||
'client_ids.*' => 'integer|distinct|exists:clients,id',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'name.required' => 'Le nom du groupe est obligatoire.',
|
||||
'name.string' => 'Le nom du groupe doit être une chaîne de caractères.',
|
||||
'name.max' => 'Le nom du groupe ne peut pas dépasser 191 caractères.',
|
||||
'name.unique' => 'Un groupe avec ce nom existe déjà.',
|
||||
'description.string' => 'La description doit être une chaîne de caractères.',
|
||||
'client_ids.array' => 'La liste des clients doit être un tableau.',
|
||||
'client_ids.*.integer' => 'Chaque ID client doit être un entier.',
|
||||
'client_ids.*.distinct' => 'Un client ne peut pas être sélectionné plusieurs fois.',
|
||||
'client_ids.*.exists' => 'Un ou plusieurs clients sélectionnés sont introuvables.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateClientLocationRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'client_id' => 'required|exists:clients,id',
|
||||
'name' => 'nullable|string|max:191',
|
||||
'address_line1' => 'nullable|string|max:255',
|
||||
'address_line2' => 'nullable|string|max:255',
|
||||
'postal_code' => 'nullable|string|max:20',
|
||||
'city' => 'nullable|string|max:191',
|
||||
'country_code' => 'nullable|string|size:2',
|
||||
'gps_lat' => 'nullable|numeric|between:-90,90',
|
||||
'gps_lng' => 'nullable|numeric|between:-180,180',
|
||||
'is_default' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'client_id.required' => 'Le client est obligatoire.',
|
||||
'client_id.exists' => 'Le client sélectionné n\'existe pas.',
|
||||
'name.max' => 'Le nom ne peut pas dépasser 191 caractères.',
|
||||
'address_line1.max' => 'L\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'address_line2.max' => 'Le complément d\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'postal_code.max' => 'Le code postal ne peut pas dépasser 20 caractères.',
|
||||
'city.max' => 'La ville ne peut pas dépasser 191 caractères.',
|
||||
'country_code.size' => 'Le code pays doit contenir 2 caractères.',
|
||||
'gps_lat.numeric' => 'La latitude doit être un nombre.',
|
||||
'gps_lat.between' => 'La latitude doit être comprise entre -90 et 90.',
|
||||
'gps_lng.numeric' => 'La longitude doit être un nombre.',
|
||||
'gps_lng.between' => 'La longitude doit être comprise entre -180 et 180.',
|
||||
'is_default.boolean' => 'Le statut par défaut doit être vrai ou faux.',
|
||||
];
|
||||
}
|
||||
}
|
||||
71
thanasoft-back/app/Http/Requests/UpdateClientRequest.php
Normal file
71
thanasoft-back/app/Http/Requests/UpdateClientRequest.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateClientRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
|
||||
'name' => 'required|string|max:255',
|
||||
'vat_number' => 'nullable|string|max:32',
|
||||
'siret' => 'nullable|string|max:20',
|
||||
'email' => 'nullable|email|max:191',
|
||||
'phone' => 'nullable|string|max:50',
|
||||
'billing_address_line1' => 'nullable|string|max:255',
|
||||
'billing_address_line2' => 'nullable|string|max:255',
|
||||
'billing_postal_code' => 'nullable|string|max:20',
|
||||
'billing_city' => 'nullable|string|max:191',
|
||||
'billing_country_code' => 'nullable|string|size:2',
|
||||
'group_id' => 'nullable|exists:client_groups,id',
|
||||
'notes' => 'nullable|string',
|
||||
'is_active' => 'boolean',
|
||||
'is_parent' => 'boolean|nullable',
|
||||
'parent_id' => 'nullable|exists:clients,id',
|
||||
'default_tva_rate_id' => 'nullable|exists:tva_rates,id',
|
||||
'avatar' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'company_id.required' => 'La société est obligatoire.',
|
||||
'company_id.exists' => 'La société sélectionnée n\'existe pas.',
|
||||
'type.required' => 'Le type de client est obligatoire.',
|
||||
'type.in' => 'Le type de client sélectionné est invalide.',
|
||||
'name.required' => 'Le nom du client est obligatoire.',
|
||||
'name.string' => 'Le nom du client doit être une chaîne de caractères.',
|
||||
'name.max' => 'Le nom du client ne peut pas dépasser 255 caractères.',
|
||||
'vat_number.max' => 'Le numéro de TVA ne peut pas dépasser 32 caractères.',
|
||||
'siret.max' => 'Le SIRET ne peut pas dépasser 20 caractères.',
|
||||
'email.email' => 'L\'adresse email doit être valide.',
|
||||
'email.max' => 'L\'adresse email ne peut pas dépasser 191 caractères.',
|
||||
'phone.max' => 'Le téléphone ne peut pas dépasser 50 caractères.',
|
||||
'billing_address_line1.max' => 'L\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'billing_address_line2.max' => 'Le complément d\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'billing_postal_code.max' => 'Le code postal ne peut pas dépasser 20 caractères.',
|
||||
'billing_city.max' => 'La ville ne peut pas dépasser 191 caractères.',
|
||||
'billing_country_code.size' => 'Le code pays doit contenir 2 caractères.',
|
||||
'group_id.exists' => 'Le groupe de clients sélectionné n\'existe pas.',
|
||||
'is_active.boolean' => 'Le statut actif doit être vrai ou faux.',
|
||||
'default_tva_rate_id.exists' => 'Le taux de TVA sélectionné n\'existe pas.',
|
||||
];
|
||||
}
|
||||
}
|
||||
71
thanasoft-back/app/Http/Requests/UpdateContactRequest.php
Normal file
71
thanasoft-back/app/Http/Requests/UpdateContactRequest.php
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateContactRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'client_id' => 'nullable|exists:clients,id',
|
||||
'fournisseur_id' => 'nullable|exists:fournisseurs,id',
|
||||
'first_name' => 'nullable|string|max:191',
|
||||
'last_name' => 'nullable|string|max:191',
|
||||
'email' => 'nullable|email|max:191',
|
||||
'phone' => 'nullable|string|max:50',
|
||||
'role' => 'nullable|string|max:191',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'client_id.exists' => 'Le client sélectionné n\'existe pas.',
|
||||
'fournisseur_id.exists' => 'Le fournisseur sélectionné n\'existe pas.',
|
||||
'first_name.string' => 'Le prénom doit être une chaîne de caractères.',
|
||||
'first_name.max' => 'Le prénom ne peut pas dépasser 191 caractères.',
|
||||
'last_name.string' => 'Le nom doit être une chaîne de caractères.',
|
||||
'last_name.max' => 'Le nom ne peut pas dépasser 191 caractères.',
|
||||
'email.email' => 'L\'adresse email doit être valide.',
|
||||
'email.max' => 'L\'adresse email ne peut pas dépasser 191 caractères.',
|
||||
'phone.max' => 'Le téléphone ne peut pas dépasser 50 caractères.',
|
||||
'role.max' => 'Le rôle ne peut pas dépasser 191 caractères.',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function withValidator($validator)
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
// At least one of client_id or fournisseur_id must be provided
|
||||
if (empty($this->client_id) && empty($this->fournisseur_id)) {
|
||||
$validator->errors()->add(
|
||||
'general',
|
||||
'Le contact doit être associé à un client ou un fournisseur.'
|
||||
);
|
||||
}
|
||||
|
||||
if (empty($this->first_name) && empty($this->last_name) && empty($this->email) && empty($this->phone)) {
|
||||
$validator->errors()->add(
|
||||
'general',
|
||||
'Au moins un champ (prénom, nom, email ou téléphone) doit être renseigné.'
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
51
thanasoft-back/app/Http/Requests/UpdateConvoyRequest.php
Normal file
51
thanasoft-back/app/Http/Requests/UpdateConvoyRequest.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateConvoyRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'deceased_id' => ['sometimes', 'required', 'exists:deceased,id'],
|
||||
'client_id' => ['nullable', 'exists:clients,id'],
|
||||
'vehicle_id' => ['nullable', 'exists:vehicles,id'],
|
||||
'mission_title' => ['nullable', 'string', 'max:255'],
|
||||
'convoy_type' => ['nullable', Rule::in(['local', 'national', 'international'])],
|
||||
'transport_mode' => ['nullable', Rule::in(['road', 'air', 'sea', 'rail'])],
|
||||
'status' => ['nullable', Rule::in(['planned', 'in_progress', 'completed', 'cancelled'])],
|
||||
'planned_start_at' => ['sometimes', 'required', 'date'],
|
||||
'estimated_end_at' => ['nullable', 'date'],
|
||||
'family_email' => ['nullable', 'email', 'max:255'],
|
||||
'automatic_notifications' => ['nullable', 'boolean'],
|
||||
'departure_location_selection_mode' => ['nullable', Rule::in(['place', 'manual'])],
|
||||
'departure_location_id' => ['nullable', 'exists:client_locations,id'],
|
||||
'departure_name' => ['nullable', 'string', 'max:255'],
|
||||
'departure_address' => ['nullable', 'string', 'max:255'],
|
||||
'departure_city' => ['nullable', 'string', 'max:255'],
|
||||
'departure_postal_code' => ['nullable', 'string', 'max:20'],
|
||||
'departure_country_code' => ['nullable', 'string', 'size:2'],
|
||||
'departure_latitude' => ['nullable', 'numeric', 'between:-90,90'],
|
||||
'departure_longitude' => ['nullable', 'numeric', 'between:-180,180'],
|
||||
'departure_additional_details' => ['nullable', 'string'],
|
||||
'tab_itinerary' => ['nullable', 'boolean'],
|
||||
'tab_legal_documents' => ['nullable', 'boolean'],
|
||||
'tab_team_resources' => ['nullable', 'boolean'],
|
||||
'tab_procedures' => ['nullable', 'boolean'],
|
||||
'tab_cost_tracking' => ['nullable', 'boolean'],
|
||||
'tab_fuel' => ['nullable', 'boolean'],
|
||||
'tab_ceremony' => ['nullable', 'boolean'],
|
||||
'tab_thanatopraxy' => ['nullable', 'boolean'],
|
||||
'tab_gps_tracking_steps' => ['nullable', 'boolean'],
|
||||
'tab_communication' => ['nullable', 'boolean'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateDeceasedDocumentRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Add your authorization logic here
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'deceased_id' => 'sometimes|required|exists:deceased,id',
|
||||
'doc_type' => 'sometimes|required|string|max:191',
|
||||
'file_id' => 'nullable|exists:files,id',
|
||||
'generated_at' => 'nullable|date',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'deceased_id.required' => 'Le défunt est obligatoire.',
|
||||
'deceased_id.exists' => 'Le défunt sélectionné n\'existe pas.',
|
||||
'doc_type.required' => 'Le type de document est obligatoire.',
|
||||
'doc_type.string' => 'Le type de document doit être une chaîne de caractères.',
|
||||
'doc_type.max' => 'Le type de document ne peut pas dépasser :max caractères.',
|
||||
'file_id.exists' => 'Le fichier sélectionné n\'existe pas.',
|
||||
'generated_at.date' => 'La date de génération doit être une date valide.',
|
||||
];
|
||||
}
|
||||
}
|
||||
49
thanasoft-back/app/Http/Requests/UpdateDeceasedRequest.php
Normal file
49
thanasoft-back/app/Http/Requests/UpdateDeceasedRequest.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateDeceasedRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
// Add authorization logic if needed
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'last_name' => ['sometimes', 'required', 'string', 'max:191'],
|
||||
'first_name' => ['nullable', 'string', 'max:191'],
|
||||
'birth_date' => ['nullable', 'date'],
|
||||
'death_date' => ['nullable', 'date', 'after_or_equal:birth_date'],
|
||||
'place_of_death' => ['nullable', 'string', 'max:255'],
|
||||
'notes' => ['nullable', 'string']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom error messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'last_name.required' => 'Le nom de famille est obligatoire.',
|
||||
'last_name.max' => 'Le nom de famille ne peut pas dépasser 191 caractères.',
|
||||
'first_name.max' => 'Le prénom ne peut pas dépasser 191 caractères.',
|
||||
'death_date.after_or_equal' => 'La date de décès doit être postérieure ou égale à la date de naissance.',
|
||||
'place_of_death.max' => 'Le lieu de décès ne peut pas dépasser 255 caractères.'
|
||||
];
|
||||
}
|
||||
}
|
||||
67
thanasoft-back/app/Http/Requests/UpdateEmployeeRequest.php
Normal file
67
thanasoft-back/app/Http/Requests/UpdateEmployeeRequest.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateEmployeeRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Add your authorization logic here
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'first_name' => 'nullable|string|max:191',
|
||||
'last_name' => 'nullable|string|max:191',
|
||||
'email' => [
|
||||
'nullable',
|
||||
'email',
|
||||
'max:191',
|
||||
Rule::unique('employees', 'email')->ignore($this->route('employee'))
|
||||
],
|
||||
'phone' => 'nullable|string|max:50',
|
||||
'user_id' => 'nullable|exists:users,id',
|
||||
'job_title' => 'nullable|string|max:191',
|
||||
'hire_date' => 'nullable|date',
|
||||
'active' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'first_name.required' => 'Le prénom est obligatoire.',
|
||||
'first_name.string' => 'Le prénom doit être une chaîne de caractères.',
|
||||
'first_name.max' => 'Le prénom ne peut pas dépasser :max caractères.',
|
||||
'last_name.required' => 'Le nom de famille est obligatoire.',
|
||||
'last_name.string' => 'Le nom de famille doit être une chaîne de caractères.',
|
||||
'last_name.max' => 'Le nom de famille ne peut pas dépasser :max caractères.',
|
||||
'email.email' => 'L\'adresse email doit être valide.',
|
||||
'email.unique' => 'Cette adresse email est déjà utilisée.',
|
||||
'phone.string' => 'Le téléphone doit être une chaîne de caractères.',
|
||||
'phone.max' => 'Le téléphone ne peut pas dépasser :max caractères.',
|
||||
'user_id.exists' => 'L\'utilisateur sélectionné est invalide.',
|
||||
'job_title.string' => 'L\'intitulé du poste doit être une chaîne de caractères.',
|
||||
'job_title.max' => 'L\'intitulé du poste ne peut pas dépasser :max caractères.',
|
||||
'hire_date.date' => 'La date d\'embauche doit être une date valide.',
|
||||
'active.boolean' => 'Le statut actif doit être un booléen.',
|
||||
];
|
||||
}
|
||||
}
|
||||
96
thanasoft-back/app/Http/Requests/UpdateFileRequest.php
Normal file
96
thanasoft-back/app/Http/Requests/UpdateFileRequest.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class UpdateFileRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
$file = $this->route('file');
|
||||
|
||||
// Allow if user owns the file or is admin
|
||||
return Auth::check() && (
|
||||
$file->uploaded_by === Auth::id() ||
|
||||
Auth::user()->hasRole('admin')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'file_name' => 'sometimes|string|max:255',
|
||||
'description' => 'nullable|string|max:500',
|
||||
'tags' => 'nullable|array|max:10',
|
||||
'tags.*' => 'string|max:50',
|
||||
'is_public' => 'boolean',
|
||||
'category' => 'sometimes|string|in:devis,facture,contrat,document,image,autre',
|
||||
'client_id' => 'nullable|integer|exists:clients,id',
|
||||
'subcategory' => 'nullable|string|max:100',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attributes for validator errors.
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'file_name' => 'nom du fichier',
|
||||
'description' => 'description',
|
||||
'tags' => 'étiquettes',
|
||||
'is_public' => 'visibilité publique',
|
||||
'category' => 'catégorie',
|
||||
'client_id' => 'client',
|
||||
'subcategory' => 'sous-catégorie',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'file_name.string' => 'Le nom du fichier doit être une chaîne de caractères.',
|
||||
'file_name.max' => 'Le nom du fichier ne peut pas dépasser 255 caractères.',
|
||||
'description.string' => 'La description doit être une chaîne de caractères.',
|
||||
'description.max' => 'La description ne peut pas dépasser 500 caractères.',
|
||||
'tags.array' => 'Les étiquettes doivent être un tableau.',
|
||||
'tags.max' => 'Vous ne pouvez pas ajouter plus de 10 étiquettes.',
|
||||
'tags.*.string' => 'Chaque étiquette doit être une chaîne de caractères.',
|
||||
'tags.*.max' => 'Chaque étiquette ne peut pas dépasser 50 caractères.',
|
||||
'is_public.boolean' => 'La visibilité publique doit être vrai ou faux.',
|
||||
'category.string' => 'La catégorie doit être une chaîne de caractères.',
|
||||
'category.in' => 'La catégorie sélectionnée n\'est pas valide.',
|
||||
'client_id.exists' => 'Le client sélectionné n\'existe pas.',
|
||||
'subcategory.string' => 'La sous-catégorie doit être une chaîne de caractères.',
|
||||
'subcategory.max' => 'La sous-catégorie ne peut pas dépasser 100 caractères.',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
// Only merge fields that are present in the request
|
||||
$data = [];
|
||||
|
||||
if ($this->has('is_public')) {
|
||||
$data['is_public'] = $this->boolean('is_public');
|
||||
}
|
||||
|
||||
$this->merge($data);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateFournisseurRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'sometimes|required|string|max:255',
|
||||
'vat_number' => 'nullable|string|max:32',
|
||||
'siret' => 'nullable|string|max:20',
|
||||
'email' => 'nullable|email|max:191',
|
||||
'phone' => 'nullable|string|max:50',
|
||||
'billing_address_line1' => 'nullable|string|max:255',
|
||||
'billing_address_line2' => 'nullable|string|max:255',
|
||||
'billing_postal_code' => 'nullable|string|max:20',
|
||||
'billing_city' => 'nullable|string|max:191',
|
||||
'billing_country_code' => 'nullable|string|size:2',
|
||||
'notes' => 'nullable|string',
|
||||
'is_active' => 'boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'name.required' => 'Le nom du fournisseur est obligatoire.',
|
||||
'name.string' => 'Le nom du fournisseur doit être une chaîne de caractères.',
|
||||
'name.max' => 'Le nom du fournisseur ne peut pas dépasser 255 caractères.',
|
||||
'vat_number.max' => 'Le numéro de TVA ne peut pas dépasser 32 caractères.',
|
||||
'siret.max' => 'Le SIRET ne peut pas dépasser 20 caractères.',
|
||||
'email.email' => 'L\'adresse email doit être valide.',
|
||||
'email.max' => 'L\'adresse email ne peut pas dépasser 191 caractères.',
|
||||
'phone.max' => 'Le téléphone ne peut pas dépasser 50 caractères.',
|
||||
'billing_address_line1.max' => 'L\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'billing_address_line2.max' => 'Le complément d\'adresse ne peut pas dépasser 255 caractères.',
|
||||
'billing_postal_code.max' => 'Le code postal ne peut pas dépasser 20 caractères.',
|
||||
'billing_city.max' => 'La ville ne peut pas dépasser 191 caractères.',
|
||||
'billing_country_code.size' => 'Le code pays doit contenir 2 caractères.',
|
||||
'is_active.boolean' => 'Le statut actif doit être vrai ou faux.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateGoodsReceiptRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'purchase_order_id' => 'sometimes|exists:purchase_orders,id',
|
||||
'warehouse_id' => 'sometimes|exists:warehouses,id',
|
||||
'receipt_number' => 'sometimes|string|max:191',
|
||||
'receipt_date' => 'sometimes|date',
|
||||
'status' => 'nullable|in:draft,posted',
|
||||
'notes' => 'nullable|string',
|
||||
'lines' => 'nullable|array',
|
||||
'lines.*.product_id' => 'required_with:lines|exists:products,id',
|
||||
'lines.*.packaging_id' => 'nullable|exists:product_packagings,id',
|
||||
'lines.*.packages_qty_received' => 'nullable|numeric|min:0',
|
||||
'lines.*.units_qty_received' => 'nullable|numeric|min:0',
|
||||
'lines.*.qty_received_base' => 'nullable|numeric|min:0',
|
||||
'lines.*.unit_price' => 'nullable|numeric|min:0',
|
||||
'lines.*.unit_price_per_package' => 'nullable|numeric|min:0',
|
||||
'lines.*.tva_rate_id' => 'nullable|exists:tva_rates,id',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'purchase_order_id.exists' => 'La commande fournisseur spécifiée n\'existe pas.',
|
||||
'warehouse_id.exists' => 'L\'entrepôt spécifié n\'existe pas.',
|
||||
'receipt_number.string' => 'Le numéro de réception doit être une chaîne de caractères.',
|
||||
'receipt_number.max' => 'Le numéro de réception ne peut pas dépasser 191 caractères.',
|
||||
'receipt_date.date' => 'La date de réception doit être une date valide.',
|
||||
'status.in' => 'Le statut doit être "draft" ou "posted".',
|
||||
'notes.string' => 'Les notes doivent être une chaîne de caractères.',
|
||||
'lines.array' => 'Les lignes doivent être un tableau.',
|
||||
'lines.*.product_id.required_with' => 'Le produit est requis pour chaque ligne.',
|
||||
'lines.*.product_id.exists' => 'Le produit spécifié dans une ligne n\'existe pas.',
|
||||
'lines.*.packaging_id.exists' => 'Le conditionnement spécifié dans une ligne n\'existe pas.',
|
||||
'lines.*.packages_qty_received.numeric' => 'La quantité de colis doit être un nombre.',
|
||||
'lines.*.packages_qty_received.min' => 'La quantité de colis ne peut pas être négative.',
|
||||
'lines.*.units_qty_received.numeric' => 'La quantité d\'unités doit être un nombre.',
|
||||
'lines.*.units_qty_received.min' => 'La quantité d\'unités ne peut pas être négative.',
|
||||
'lines.*.qty_received_base.numeric' => 'La quantité de base doit être un nombre.',
|
||||
'lines.*.qty_received_base.min' => 'La quantité de base ne peut pas être négative.',
|
||||
'lines.*.unit_price.numeric' => 'Le prix unitaire doit être un nombre.',
|
||||
'lines.*.unit_price.min' => 'Le prix unitaire ne peut pas être négatif.',
|
||||
'lines.*.unit_price_per_package.numeric' => 'Le prix par colis doit être un nombre.',
|
||||
'lines.*.unit_price_per_package.min' => 'Le prix par colis ne peut pas être négatif.',
|
||||
'lines.*.tva_rate_id.exists' => 'Le taux de TVA spécifié dans une ligne n\'existe pas.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateInterventionRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
// Add authorization logic if needed
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'client_id' => ['sometimes', 'required', 'exists:clients,id'],
|
||||
'deceased_id' => ['nullable', 'exists:deceased,id'],
|
||||
'order_giver' => ['nullable', 'string', 'max:255'],
|
||||
'location_id' => ['nullable', 'exists:client_locations,id'],
|
||||
'product_id' => ['nullable', 'exists:products,id'],
|
||||
'type' => ['sometimes', 'required', Rule::in([
|
||||
'thanatopraxie',
|
||||
'toilette_mortuaire',
|
||||
'exhumation',
|
||||
'retrait_pacemaker',
|
||||
'retrait_bijoux',
|
||||
'autre'
|
||||
])],
|
||||
'scheduled_at' => ['nullable', 'date_format:Y-m-d H:i:s'],
|
||||
'duration_min' => ['nullable', 'integer', 'min:0'],
|
||||
'status' => ['sometimes', Rule::in([
|
||||
'demande',
|
||||
'planifie',
|
||||
'en_cours',
|
||||
'termine',
|
||||
'annule'
|
||||
])],
|
||||
'practitioners' => ['nullable', 'array'],
|
||||
'practitioners.*' => ['exists:thanatopractitioners,id'],
|
||||
'principal_practitioner_id' => ['nullable', 'exists:thanatopractitioners,id'],
|
||||
'assistant_practitioner_ids' => ['nullable', 'array'],
|
||||
'assistant_practitioner_ids.*' => ['exists:thanatopractitioners,id'],
|
||||
'notes' => ['nullable', 'string'],
|
||||
'created_by' => ['nullable', 'exists:users,id']
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom error messages for validator errors.
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'client_id.required' => 'Le client est obligatoire.',
|
||||
'client_id.exists' => 'Le client sélectionné est invalide.',
|
||||
'deceased_id.exists' => 'Le défunt sélectionné est invalide.',
|
||||
'order_giver.max' => 'Le donneur d\'ordre ne peut pas dépasser 255 caractères.',
|
||||
'location_id.exists' => 'Le lieu sélectionné est invalide.',
|
||||
'type.required' => 'Le type d\'intervention est obligatoire.',
|
||||
'type.in' => 'Le type d\'intervention est invalide.',
|
||||
'scheduled_at.date_format' => 'Le format de la date programmée est invalide.',
|
||||
'duration_min.integer' => 'La durée doit être un nombre entier.',
|
||||
'duration_min.min' => 'La durée ne peut pas être négative.',
|
||||
'status.in' => 'Le statut de l\'intervention est invalide.',
|
||||
'practitioners.array' => 'Les praticiens doivent être un tableau.',
|
||||
'practitioners.*.exists' => 'Un des praticiens sélectionnés est invalide.',
|
||||
'principal_practitioner_id.exists' => 'Le praticien principal sélectionné est invalide.',
|
||||
'assistant_practitioner_ids.array' => 'Les praticiens assistants doivent être un tableau.',
|
||||
'assistant_practitioner_ids.*.exists' => 'Un des praticiens assistants est invalide.',
|
||||
'created_by.exists' => 'L\'utilisateur créateur est invalide.'
|
||||
];
|
||||
}
|
||||
}
|
||||
93
thanasoft-back/app/Http/Requests/UpdateInvoiceRequest.php
Normal file
93
thanasoft-back/app/Http/Requests/UpdateInvoiceRequest.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateInvoiceRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$invoiceId = $this->route('invoice');
|
||||
|
||||
return [
|
||||
'client_id' => 'nullable|exists:clients,id',
|
||||
'group_id' => 'nullable|exists:client_groups,id',
|
||||
'source_quote_id' => 'nullable|exists:quotes,id',
|
||||
'invoice_number' => 'sometimes|string|max:191|unique:invoices,invoice_number,' . $invoiceId,
|
||||
'status' => 'sometimes|in:brouillon,emise,envoyee,partiellement_payee,payee,echue,annulee,avoir',
|
||||
'invoice_date' => 'sometimes|date',
|
||||
'due_date' => 'nullable|date|after_or_equal:invoice_date',
|
||||
'currency' => 'sometimes|string|size:3',
|
||||
'total_ht' => 'sometimes|numeric|min:0',
|
||||
'total_tva' => 'sometimes|numeric|min:0',
|
||||
'total_ttc' => 'sometimes|numeric|min:0',
|
||||
'e_invoicing_channel_id' => 'nullable|exists:e_invoicing_channels,id',
|
||||
'e_invoice_status' => 'nullable|in:cree,transmis,accepte,refuse,en_litige,acquitte,archive',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'client_id.exists' => 'Le client sélectionné est invalide.',
|
||||
'group_id.exists' => 'Le groupe sélectionné est invalide.',
|
||||
'source_quote_id.exists' => 'Le devis source est invalide.',
|
||||
'invoice_number.string' => 'Le numéro de facture doit être une chaîne de caractères.',
|
||||
'invoice_number.max' => 'Le numéro de facture ne doit pas dépasser 191 caractères.',
|
||||
'invoice_number.unique' => 'Ce numéro de facture existe déjà.',
|
||||
'status.in' => 'Le statut sélectionné est invalide.',
|
||||
'invoice_date.date' => 'La date de la facture n\'est pas valide.',
|
||||
'due_date.date' => 'La date d\'échéance n\'est pas valide.',
|
||||
'due_date.after_or_equal' => 'La date d\'échéance doit être postérieure ou égale à la date de la facture.',
|
||||
'currency.size' => 'La devise doit comporter 3 caractères.',
|
||||
'total_ht.numeric' => 'Le total HT doit être un nombre.',
|
||||
'total_ht.min' => 'Le total HT ne peut pas être négatif.',
|
||||
'total_tva.numeric' => 'Le total TVA doit être un nombre.',
|
||||
'total_tva.min' => 'Le total TVA ne peut pas être négatif.',
|
||||
'total_ttc.numeric' => 'Le total TTC doit être un nombre.',
|
||||
'total_ttc.min' => 'Le total TTC ne peut pas être négatif.',
|
||||
'e_invoicing_channel_id.exists' => 'Le canal de facturation électronique est invalide.',
|
||||
'e_invoice_status.in' => 'Le statut de facturation électronique est invalide.',
|
||||
];
|
||||
}
|
||||
|
||||
public function withValidator($validator): void
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
if (! $this->hasAny(['client_id', 'group_id'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$hasClient = filled($this->input('client_id'));
|
||||
$hasGroup = filled($this->input('group_id'));
|
||||
|
||||
if (! $hasClient && ! $hasGroup) {
|
||||
$message = 'Un client ou un groupe de clients est obligatoire.';
|
||||
|
||||
$validator->errors()->add('client_id', $message);
|
||||
$validator->errors()->add('group_id', $message);
|
||||
}
|
||||
|
||||
if ($hasClient && $hasGroup) {
|
||||
$message = 'Selectionnez soit un client, soit un groupe de clients.';
|
||||
|
||||
$validator->errors()->add('client_id', $message);
|
||||
$validator->errors()->add('group_id', $message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdatePractitionerDocumentRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true; // Add your authorization logic here
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'practitioner_id' => 'required|exists:thanatopractitioners,id',
|
||||
'doc_type' => 'required|string|max:191',
|
||||
'file_id' => 'nullable|exists:files,id',
|
||||
'issue_date' => 'nullable|date',
|
||||
'expiry_date' => 'nullable|date|after_or_equal:issue_date',
|
||||
'status' => 'nullable|string|max:64',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'practitioner_id.required' => 'Le thanatopractitioner est obligatoire.',
|
||||
'practitioner_id.exists' => 'Le thanatopractitioner sélectionné n\'existe pas.',
|
||||
'doc_type.required' => 'Le type de document est obligatoire.',
|
||||
'doc_type.string' => 'Le type de document doit être une chaîne de caractères.',
|
||||
'doc_type.max' => 'Le type de document ne peut pas dépasser :max caractères.',
|
||||
'file_id.exists' => 'Le fichier sélectionné n\'existe pas.',
|
||||
'issue_date.date' => 'La date de délivrance doit être une date valide.',
|
||||
'expiry_date.date' => 'La date d\'expiration doit être une date valide.',
|
||||
'expiry_date.after_or_equal' => 'La date d\'expiration doit être égale ou postérieure à la date de délivrance.',
|
||||
'status.string' => 'Le statut doit être une chaîne de caractères.',
|
||||
'status.max' => 'Le statut ne peut pas dépasser :max caractères.',
|
||||
];
|
||||
}
|
||||
}
|
||||
54
thanasoft-back/app/Http/Requests/UpdatePriceListRequest.php
Normal file
54
thanasoft-back/app/Http/Requests/UpdatePriceListRequest.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdatePriceListRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$routePriceList = $this->route('price_list');
|
||||
$priceListId = is_object($routePriceList)
|
||||
? $routePriceList->id
|
||||
: ($routePriceList ?? $this->route('id'));
|
||||
|
||||
return [
|
||||
'name' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:191',
|
||||
Rule::unique('price_lists', 'name')->ignore($priceListId),
|
||||
],
|
||||
'valid_from' => 'nullable|date',
|
||||
'valid_to' => 'nullable|date|after_or_equal:valid_from',
|
||||
'is_default' => 'nullable|boolean',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'name.required' => 'Le nom de la liste de prix est obligatoire.',
|
||||
'name.unique' => 'Une liste de prix avec ce nom existe déjà.',
|
||||
'valid_from.date' => 'La date de début doit être une date valide.',
|
||||
'valid_to.date' => 'La date de fin doit être une date valide.',
|
||||
'valid_to.after_or_equal' => 'La date de fin doit être postérieure ou égale à la date de début.',
|
||||
'is_default.boolean' => 'Le statut par défaut doit être vrai ou faux.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class UpdateProductCategoryRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'code' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:64',
|
||||
Rule::unique('product_categories')->ignore($this->route('product_category')),
|
||||
],
|
||||
'name' => 'required|string|max:191',
|
||||
'description' => 'nullable|string',
|
||||
'parent_id' => [
|
||||
'nullable',
|
||||
'exists:product_categories,id',
|
||||
// Prevent setting parent to itself
|
||||
function ($attribute, $value, $fail) {
|
||||
if ($this->route('product_category') && $value == $this->route('product_category')->id) {
|
||||
$fail('The parent category cannot be the category itself.');
|
||||
}
|
||||
},
|
||||
],
|
||||
'intervention' => 'boolean',
|
||||
'active' => 'boolean',
|
||||
];
|
||||
}
|
||||
}
|
||||
91
thanasoft-back/app/Http/Requests/UpdateProductRequest.php
Normal file
91
thanasoft-back/app/Http/Requests/UpdateProductRequest.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateProductRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$productId = $this->route('id');
|
||||
|
||||
return [
|
||||
'nom' => 'required|string|max:255',
|
||||
'reference' => "nullable",
|
||||
'categorie_id' => 'required|exists:product_categories,id',
|
||||
'fabricant' => 'nullable|string|max:191',
|
||||
'stock_actuel' => 'required|numeric|min:0',
|
||||
'stock_minimum' => 'required|numeric|min:0',
|
||||
'unite' => 'required|string|max:50',
|
||||
'prix_unitaire' => 'required|numeric|min:0',
|
||||
'date_expiration' => 'nullable|date|after:today',
|
||||
'numero_lot' => 'nullable|string|max:100',
|
||||
'conditionnement_nom' => 'nullable|string|max:191',
|
||||
'conditionnement_quantite' => 'nullable|numeric|min:0',
|
||||
'conditionnement_unite' => 'nullable|string|max:50',
|
||||
'image' => 'nullable|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
|
||||
'remove_image' => 'nullable|boolean',
|
||||
'fiche_technique_url' => 'nullable|url|max:500',
|
||||
'fournisseur_id' => 'nullable|exists:fournisseurs,id',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'nom.required' => 'Le nom du produit est obligatoire.',
|
||||
'nom.string' => 'Le nom du produit doit être une chaîne de caractères.',
|
||||
'nom.max' => 'Le nom du produit ne peut pas dépasser 255 caractères.',
|
||||
'reference.required' => 'La référence du produit est obligatoire.',
|
||||
'reference.string' => 'La référence du produit doit être une chaîne de caractères.',
|
||||
'reference.max' => 'La référence du produit ne peut pas dépasser 100 caractères.',
|
||||
'reference.unique' => 'Cette référence de produit existe déjà.',
|
||||
'categorie_id.required' => 'La catégorie est obligatoire.',
|
||||
'categorie_id.exists' => 'La catégorie sélectionnée n\'existe pas.',
|
||||
'fabricant.string' => 'Le fabricant doit être une chaîne de caractères.',
|
||||
'fabricant.max' => 'Le fabricant ne peut pas dépasser 191 caractères.',
|
||||
'stock_actuel.required' => 'Le stock actuel est obligatoire.',
|
||||
'stock_actuel.numeric' => 'Le stock actuel doit être un nombre.',
|
||||
'stock_actuel.min' => 'Le stock actuel doit être supérieur ou égal à 0.',
|
||||
'stock_minimum.required' => 'Le stock minimum est obligatoire.',
|
||||
'stock_minimum.numeric' => 'Le stock minimum doit être un nombre.',
|
||||
'stock_minimum.min' => 'Le stock minimum doit être supérieur ou égal à 0.',
|
||||
'unite.required' => 'L\'unité est obligatoire.',
|
||||
'unite.string' => 'L\'unité doit être une chaîne de caractères.',
|
||||
'unite.max' => 'L\'unité ne peut pas dépasser 50 caractères.',
|
||||
'prix_unitaire.required' => 'Le prix unitaire est obligatoire.',
|
||||
'prix_unitaire.numeric' => 'Le prix unitaire doit être un nombre.',
|
||||
'prix_unitaire.min' => 'Le prix unitaire doit être supérieur ou égal à 0.',
|
||||
'date_expiration.date' => 'La date d\'expiration doit être une date valide.',
|
||||
'date_expiration.after' => 'La date d\'expiration doit être postérieure à aujourd\'hui.',
|
||||
'numero_lot.string' => 'Le numéro de lot doit être une chaîne de caractères.',
|
||||
'numero_lot.max' => 'Le numéro de lot ne peut pas dépasser 100 caractères.',
|
||||
'conditionnement_nom.string' => 'Le nom du conditionnement doit être une chaîne de caractères.',
|
||||
'conditionnement_nom.max' => 'Le nom du conditionnement ne peut pas dépasser 191 caractères.',
|
||||
'conditionnement_quantite.numeric' => 'La quantité du conditionnement doit être un nombre.',
|
||||
'conditionnement_quantite.min' => 'La quantité du conditionnement doit être supérieure ou égal à 0.',
|
||||
'conditionnement_unite.string' => 'L\'unité du conditionnement doit être une chaîne de caractères.',
|
||||
'conditionnement_unite.max' => 'L\'unité du conditionnement ne peut pas dépasser 50 caractères.',
|
||||
'image.image' => 'Le fichier doit être une image valide.',
|
||||
'image.mimes' => 'L\'image doit être de type: jpeg, png, jpg, gif ou svg.',
|
||||
'image.max' => 'L\'image ne peut pas dépasser 2MB.',
|
||||
'fiche_technique_url.url' => 'L\'URL de la fiche technique doit être une URL valide.',
|
||||
'fiche_technique_url.max' => 'L\'URL de la fiche technique ne peut pas dépasser 500 caractères.',
|
||||
'fournisseur_id.exists' => 'Le fournisseur sélectionné n\'existe pas.',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdatePurchaseOrderRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'fournisseur_id' => ['nullable', 'exists:fournisseurs,id'],
|
||||
'po_number' => ['nullable', 'string', 'max:191', 'unique:purchase_orders,po_number,' . $this->route('purchase_order')],
|
||||
'status' => ['nullable', 'in:brouillon,confirmee,livree,facturee,annulee'],
|
||||
'order_date' => ['nullable', 'date'],
|
||||
'expected_date' => ['nullable', 'date'],
|
||||
'currency' => ['nullable', 'string', 'size:3'],
|
||||
'total_ht' => ['nullable', 'numeric', 'min:0'],
|
||||
'total_tva' => ['nullable', 'numeric', 'min:0'],
|
||||
'total_ttc' => ['nullable', 'numeric', 'min:0'],
|
||||
'notes' => ['nullable', 'string'],
|
||||
'delivery_address' => ['nullable', 'string'],
|
||||
'lines' => ['nullable', 'array', 'min:1'],
|
||||
'lines.*.product_id' => ['nullable', 'exists:products,id'],
|
||||
'lines.*.description' => ['required', 'string'],
|
||||
'lines.*.quantity' => ['required', 'numeric', 'min:0.001'],
|
||||
'lines.*.unit_price' => ['required', 'numeric', 'min:0'],
|
||||
'lines.*.tva_rate' => ['required', 'numeric', 'min:0'],
|
||||
'lines.*.discount_pct' => ['nullable', 'numeric', 'min:0', 'max:100'],
|
||||
'lines.*.total_ht' => ['required', 'numeric', 'min:0'],
|
||||
];
|
||||
}
|
||||
}
|
||||
39
thanasoft-back/app/Http/Requests/UpdateQuoteLineRequest.php
Normal file
39
thanasoft-back/app/Http/Requests/UpdateQuoteLineRequest.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateQuoteLineRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'quote_id' => 'sometimes|exists:quotes,id',
|
||||
'product_id' => 'nullable|exists:products,id',
|
||||
'packaging_id' => 'nullable|exists:product_packagings,id',
|
||||
'packages_qty' => 'nullable|numeric|min:0',
|
||||
'units_qty' => 'nullable|numeric|min:0',
|
||||
'description' => 'sometimes|string',
|
||||
'qty_base' => 'nullable|numeric|min:0',
|
||||
'unit_price' => 'sometimes|numeric|min:0',
|
||||
'unit_price_per_package' => 'nullable|numeric|min:0',
|
||||
'tva_rate_id' => 'nullable|exists:tva_rates,id',
|
||||
'discount_pct' => 'sometimes|numeric|min:0|max:100',
|
||||
'total_ht' => 'sometimes|numeric|min:0',
|
||||
];
|
||||
}
|
||||
}
|
||||
61
thanasoft-back/app/Http/Requests/UpdateQuoteRequest.php
Normal file
61
thanasoft-back/app/Http/Requests/UpdateQuoteRequest.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateQuoteRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$quoteId = $this->route('quote');
|
||||
|
||||
return [
|
||||
'client_id' => 'sometimes|exists:clients,id',
|
||||
'group_id' => 'nullable|exists:client_groups,id',
|
||||
'reference' => 'sometimes|string|max:191|unique:quotes,reference,' . $quoteId,
|
||||
'status' => 'sometimes|in:brouillon,envoye,accepte,refuse,expire,annule',
|
||||
'quote_date' => 'sometimes|date',
|
||||
'valid_until' => 'nullable|date|after_or_equal:quote_date',
|
||||
'currency' => 'sometimes|string|size:3',
|
||||
'total_ht' => 'sometimes|numeric|min:0',
|
||||
'total_tva' => 'sometimes|numeric|min:0',
|
||||
'total_ttc' => 'sometimes|numeric|min:0',
|
||||
];
|
||||
}
|
||||
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'client_id.exists' => 'Le client sélectionné est invalide.',
|
||||
'group_id.exists' => 'Le groupe sélectionné est invalide.',
|
||||
'reference.string' => 'Le numéro de devis doit être une chaîne de caractères.',
|
||||
'reference.max' => 'Le numéro de devis ne doit pas dépasser 191 caractères.',
|
||||
'reference.unique' => 'Ce numéro de devis existe déjà.',
|
||||
'status.in' => 'Le statut sélectionné est invalide.',
|
||||
'quote_date.date' => 'La date du devis n\'est pas valide.',
|
||||
'valid_until.date' => 'La date de validité n\'est pas valide.',
|
||||
'valid_until.after_or_equal' => 'La date de validité doit être postérieure ou égale à la date du devis.',
|
||||
'currency.size' => 'La devise doit comporter 3 caractères.',
|
||||
'total_ht.numeric' => 'Le total HT doit être un nombre.',
|
||||
'total_ht.min' => 'Le total HT ne peut pas être négatif.',
|
||||
'total_tva.numeric' => 'Le total TVA doit être un nombre.',
|
||||
'total_tva.min' => 'Le total TVA ne peut pas être négatif.',
|
||||
'total_ttc.numeric' => 'Le total TTC doit être un nombre.',
|
||||
'total_ttc.min' => 'Le total TTC ne peut pas être négatif.',
|
||||
];
|
||||
}
|
||||
}
|
||||
42
thanasoft-back/app/Http/Requests/UpdateStockItemRequest.php
Normal file
42
thanasoft-back/app/Http/Requests/UpdateStockItemRequest.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class UpdateStockItemRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'qty_on_hand_base' => 'sometimes|numeric|min:0',
|
||||
'safety_stock_base' => 'sometimes|numeric|min:0',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error messages for the defined validation rules.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function messages(): array
|
||||
{
|
||||
return [
|
||||
'qty_on_hand_base.numeric' => 'La quantité en stock doit être un nombre.',
|
||||
'safety_stock_base.numeric' => 'Le stock de sécurité doit être un nombre.',
|
||||
];
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user