name: CI/CD - ERP Core Monorepo
on:
pull_request:
branches:
- production
types:
- closed
jobs:
backend-build-and-deploy:
name: Build & Deploy ERP Core Backend
runs-on: linux
if: github.event.pull_request.merged == true
steps:
- name: Checkout code
run: |
git clone \
--depth 1 \
--branch production \
https://oauth2:${{ secrets.ERPCORE }}@gitea.hexdive.com/New_REP_SYSTEM/ERP-core.git \
.
- name: Restore backend
run: dotnet restore Backend/ERPCore.sln
- name: Publish backend
run: |
rm -rf /tmp/erpcore-deploy
mkdir -p /tmp/erpcore-deploy
dotnet publish Backend/ERPCore/ERPCore.csproj \
-c Release \
-r linux-x64 \
--self-contained true \
-o /tmp/erpcore-deploy
- name: Stop backend service
run: systemctl stop erpcore.service
- name: Copy backend to deploy folder
run: |
mkdir -p /home/erp/core/publish
rm -rf /home/erp/core/publish/*
cp -r /tmp/erpcore-deploy/* /home/erp/core/publish/
- name: Fix backend ownership and permissions
run: |
chown -R erpuser:erpuser /home/erp/core/publish
find /home/erp/core/publish -type d -exec chmod 755 {} +
find /home/erp/core/publish -type f -exec chmod 644 {} +
chmod +x /home/erp/core/publish/ERPCore
- name: Restart backend service
run: systemctl restart erpcore.service
- name: Verify backend
run: |
systemctl is-active erpcore.service && echo "Backend service running" || echo "Backend service failed"
frontend-build-and-deploy:
name: Build & Deploy ERP Core Frontend
runs-on: linux
needs: backend-build-and-deploy
if: github.event.pull_request.merged == true
steps:
- name: Checkout code
run: |
git clone \
--depth 1 \
--branch Production \
https://oauth2:${{ secrets.ERPCORE }}@gitea.hexdive.com/New_REP_SYSTEM/ERP-core.git \
.
- name: Build frontend Docker image
run: docker build -t erpcore-frontend:latest Frontend/erp-system
- name: Stop & remove existing frontend container
run: |
docker stop erpcore-frontend 2>/dev/null || true
docker rm erpcore-frontend 2>/dev/null || true
- name: Start frontend container
run: |
docker run -d \
--name erpcore-frontend \
-p 6702:6702 \
--add-host=host.docker.internal:host-gateway \
-e BACKEND_ORIGIN=http://host.docker.internal:6701 \
--restart unless-stopped \
--memory="512m" \
--cpus="1.0" \
--cap-drop ALL \
--security-opt no-new-privileges \
erpcore-frontend:latest
- name: Remove old frontend images
run: docker image prune -f
- name: Verify frontend
run: |
docker ps --filter name=erpcore-frontend --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" \
| grep erpcore-frontend && echo "Frontend container running" || echo "Frontend container failed"
This commit is contained in:
@@ -0,0 +1 @@
|
||||
False
|
||||
@@ -0,0 +1 @@
|
||||
bf7d0202-9a86-43b3-8323-402073a8c61a
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTyW7bMBD9FYFnUaFIaqFuSeygRpDaiI226I3LKCaikAZFZUHgfy8oO0jTWy8EMeCbecvwHTn5BKhDl4fD8hlcHFGOop1LlNAaE4HLZkeqjrcdF0VZcUoZ+41yZG/hDXWo4VqXWgBmUAPmhlRYsV5jY0CUpDSEVjz1lA8j6t6RtIWBZ6uh8OMPCKP1DnXop3XGv6TZ0hZ68JMpgh9g5cYonU5kFsvt7W69wTdN/X15dXV6OcKYGhTWoA61VUW10AJzISrMdcOx7BuKe6r6UpVNRQScYNZFCE4OxWgePzkYHx1E3dGCsoJgKtAxR0ZGmWgrOcLu7ZCYzDYtUj2fy/O1e0fPEFBH8w9DT+0u9GAvnkY12cFcnM4ox8ek9BD8AUK0MPsySx0GGa132XnS9WDBRZSj9TbbDDL2Pjx9MWt70r/6D/m7NH35CnqKYK795GLCohzdSb23DrLVAnWoLrnhghjMSyUxb3qBJTCJVVUL3ipmakihXnsXrZv8NGYrF+EhzPRRh27kMKZp6212GfTeRtBxCknTrzoBN8GbScfs0/2SFKRghCXQFA9TzO7B2AA6QlK3C9O53z8QWlNCEpUpBHAx28i4z77JcZ8emMZUmgKYkqmGqB6q1rRVqxtT123da15pwspKc65Zr4yQSgmmKae94tDUKEeQws7m/Wr6viW1ajEXjcJcMo6VIBzzsq4kV9JUMiE+fczWQ8KpvjGEEoqFbGvMmWK4ZZRhTihpmGx1XaZV+rDknD1PmdxCcDD8JfnO6uBH38fsvATZFxPuJ5f+bjYvxIt1+HV2e+H1I4QspSWtS2t6Duh4PB7/AAAA//8DAAY6R+UCBAAA
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTyU7dMBT9lcjrOHiKHWfHqCJEQYDaqjvHvuFZBPvJcRiE3r9XDg9RuuvOuvI5PsP1GwrmEVCPDrfb0ycIeUY1yn4dMcIkJhpTdUfaXnS90A2VHVdK/UY18hfwinqkhLXUasAcJGDhSIsHPlrsHGhKqCOsFYXT3M+of0PGNw6evIUmzj8gzT4G1KOfPrj4XN42vrFTXFyT4gTnYc4m2CLm5PT24u7qGp8p+f306Oj95gxzIWi8Qz3q2pZZbTUWWrdYWCWwGRXDIxtGOlDVEg3vMB8ypGCmZnYPnxpczAGy7VnDeEMw02hXI2eyKbIHM8Pd67YoWWM6KfN6Ha/H/g09QUI9qz8Cfac7sJM/sPHx0QR3MPrg5w2q0TbFLaTsYY1kdTlNJvsYqv0jx5OHkFGNrm6r68nkMabHLzndvls//w/nl8ZufIDq/AT1SFLhhCYOCzoYLNSosQFu8NBKLbqBOwmltuMYsg9LXObqPGS4T6tK1KMzM82F9Oq2Okx24zPYvKQi/ZcsQHjx+Ti6MiCoRtcpusXm6jNuShrScMILx5K3S65uwPkENkPxdJeWPf0/ECYZKYzHS0oQcnVt8qb6ZuZNueCUay0DcJQPigwjtJ3r2s4qJ2UnRytaSzhtrRCWj4PTZhg0t0ywcRCgZNFd2q3WhQJCmNCK4lGqFgupKDbcScwJjAoYpa4riM9Yq6up4IZROcIIw9p0Egs+cNxxxrEgjChuOitp2Z2PSPaNiw7V6AJSgOkvy5fepjjHMVf76qsvIdwsoXzWal2DZx/wyxr+SbQPkKpSnvGh7OW+r91ut/sDAAD//wMAfxRohPMDAAA=
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTS0/jMBD+K5HPdXBsJ3FyA1q0FWKpoNpd7c0eT6hFcCrH4SHU/75yWsSyt73E0cgz8738Trx+QtKS8/1+9Yw+jmRBoptLnPGKsoYW9ZaVrVStbHKlmkpU8jdZEHeNb6QltQQooEEqsEIqLSupER1Qa7EpWGEZL2WaqR9G0r4T7XKLzw4wH8YfGEY3eNKSn87b4SXt1i6HfphsHoYe136M2kMCs1zdX29vN/Sqrr6vLi6ON0cc04DcWdKSqkOjrC5p3TRAJTSGqrqpqdaisx2W6Ty2OR8xeN3no338xGCH6DFCy3MuckZ5Qw4LYnXUCbbRI27f9gnJLNMy1Rdzef5t38kzBtLyxYegx3Fn0Luzp9FMrrdnx2/U42Niug/DHkN0OOsyU+17Hd3gs9Omy96hj2RBbu+zTa9jN4SnL2LdH/mv/4P+Nm1fvSJMEe3lMPlIWqLIgtxo2DmP2XqZhhXSyoZZKgujqay7hmoUmpqyaqQywlaYTL0cfHR+GqYxW/uID2GGT1pypfsRj8DPA+xcRIhTSJx+ValxEwY7Qcw+1S9YznLBRGqa4n6K2R1aFxAiJnbbMJ3m/dPCK85YgjKFgD5mGx132Tc97tIFW9sSOKIthKmZ6bBUVpUKaltVqupAlsBEUYKUIDpjG21MI4BL3hmJdUUWBJPZ2ZwvbQ2zrKip6rim0uqCKgBGrapBAGCBUHzRMbvtU5/pass447TRqqJSGEGV4IJKxlkttIKqSFH6kOTkvUyeXGPw2P9F+cZBGMahi9kpBNkXEe4mn95uNgfixXn6Oqu9HOARQ5bc0s6nmJ4MOhwOhz8AAAD//wMACwdnnQIEAAA=
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSTS2/cIBSF/4rF2jgYsA3e5alGUZooGbVVdzyuExQHRoDzUDT/vcIzVZvuusHoinM5fOf6A3n1DGhEx9vt+Qv4nFCNsltLlNAeE4nbYUO6kYuRy0aInhJJfqIauSt4RyMauDGtkYAZ9IC5JR3WbDLYWpAtaS2hHS891UNC4wdSrrHw4gw0IX2DmFzwaETfnbfhtdytXGPmsNgmhhkufcrKm2Lm7Pz+anNziy+G/uv5ycn+ZIJUGjTOohH1E2hhVYcHKQ3mRmosBjlgpdhkJ+jKdy9zPkP0am6SffrjwYbsIZuRNpQ1BFOJdjWyKqtiW6sEm/dtcbJiOiv1ei2v2/EDvUBEI61/A923OzKzO3pOenGzPdqvWaWntK6TMjnEd1SjbQxbiNnByug4JXjW8/tGpaeL/ZmyTedvYJYM9jQsPqMRCVSjFdE8q+yCrw4OT2cHPqMa3dxXt7PKU4jPnyDf77ld/ge2a2UenYfq8qxoWm65JBbzVivMh0liBUxh3fWSC81sDyXz0+Cz80tYUnXpMzzE1SUa0YWaE+z9HUfz6DKYvMRi/UdfhLcx2MXk6k84LWlIwwgroiVvl1zdgXURTIbyiE1cDv3+kdCeElKsLDGCz9Wtyo/VF5UeywE72M5QANsyPRA9QSes6IQZbN+LfjK8M4S1neHcsElbqbSWzFBOJ81h6FGNoMxCtY6fsUQTbQQeqLGYc9liwcSAmSJaU2HEpOUnjtXNXHR6GiyhhGKpRI850wwLRhnmhJKBKWH6tkzabySHiHnJ/gqih/mvJ187E0MKU64OWVefINwtvvza1Zr7q/P4baV9FswTxKqkpZwvU3wIaLfb7X4BAAD//wMA4dUdUiEEAAA=
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUTW/cNhCG/4rAsyhTJMUP3ZLYQV0j9SJeNEVvQ3JkC9FSC4raNDD2vxfUbuCmt5xIDPgOZx6+nFcS4YCkJ++Ox7sTxryQmuRxC3HGFWWWtnrPul6aXtpGWGN5K/8mNRkf8DvpiZbet94iFaiQysA66sTgaQhoW9YGxjtZcsLzQvpXAmMT8DR6bOblT0zLOEfSky9jDPO3cjeMjZ/mNTRpnvA+LhmiL8Xc3j097B939KNWf9y9f385ueBSEjRjID1RAzoToKPaWk+lt44abTUFEEMYsCvrRTbGjCnC1Czh61sNYc4Rs+95w0XDKLfkXJMAGUrZDhbcfz+WSjZMtyVeb+Ft27+SEybS8/oH0Eu6Gz+NN3k+TnjC6QhpwXTj58MBYiA1Oab5iCmPuKHZup0myOMcq+tlH6YRYyY1eXyqdhPkYU6Hn3g9XRDc/wKBEyZHetKZDkSrEWQwaMOgOuS2E6ZlKCUIzQODYZBK+KCYVQMCD156r1vOgxWK1OR5mh1Mvy8bP8uZM4FprjhKtFwqZT1DYXDoNLIASjllvA3CaC5NMAIsQGsdam+VLyb5BP5ljFjd35ZuWhmkZYHK1gGVerAUUAB1nbLSOBEUFs2HOeYxrvO6VPcx43Pa+JGefIRpwQu5d8m/jBl9XlOB+pcqwl2aw+pz9eaAljWsEUwU0ZqPa64+YxgT+owF7z6t13z/k3DFGSulrClhzNUO8kv1Gywv5UDQofMcMbTCaeYG7EwwnfE6KGXU4GXnmWg7L6UXgwsWnLPCc8kHJ1EXylgMV20ed8xLaNFT7V1HJVhDQYKhxjDDW9VxZouv3jhWj9OmG3RgnHFqwSgqhRPUCC6oZJxpAcarttj5B5Kr+aQhNXnAFHH6T8ufRp/mZR5ydXVh9ROEz2ss86PaHPltjPSfjfbt7L9iqsprwRjLV7k+0LkmB4RlTXjYxk//Snbln1T7bQpx1TBjTE2eMqS8Hq/hrmsMk+fz+fwvAAAA//8DALVuXjbCBAAA
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRU207kOBD9lcivGwffEtt5A7rRIsRF063d1b75UgEvaaflODAI9b+vkoYBZrQP+xJHJVfVqXOO6xVFswPUotP9fv0EMY+oRDksIUZYg4nGVG5J3QrVCl0pzZlk9d+oROEKXlCLpHCOOg2YQwNYeFJjyzuHvQdNCfWE1WKuae5H1L4iEyoPT8FBNYx/QBrDEFGL/gzRD89zbxMq1w+Tr9LQw2Ucs4luBrNab662t3f4QjY367Oz480RxrlAFTxqUdOBVd7UWGrtsHDaYiW1xMbwzndQz+cxLcQMKZq+Gv3jBwY/5AjZtaxivCKYaXQokTfZzLCtGWH7sp+RLDSt5ni5hJff9hU9QUItK98JPZY7cX042Y12Cr0/Wb6oRPs07CHlAAsjy5B9b3IYYvHW47wPEDMq0dmcspmcg3FELdqmCd6j63gfIlwks4PnIT3eHNtWN+stKtFdGv4Bl+9MfkAt4p1TvrOOGqFtZ8HrhtQGtOmEFarznlPXOUY70khpmei48taappG1t5YxVKLbTXHXm9wNafdFr81Rgsv/ocDm9Hwdje1hzrkw/fhjpK1J95BRiyylnVNSa+FZrT0H4xQI2lhPjFIcBHgute6o4pxxRTtFrNKmVsTWIDxHJbocN9lEb/ohwvo7uGnm94PCa+MeQoTicjUDp8ILTTwW1BosZKexAW6wrRstlOW+gdnD50PMIU7DNBaXMcN9WjT7NMTtpjhN7iFkcHlKsxx/NXPiXRr85HLxYTZKKlJxMgO9nfJ+ysU38CGBywsrbyBvNz+nsIYRMkOZUoKYi1ng4nczzipTL33tGICn3EpiO6iVV7Vy0jeNajonakc4rZ0QjnfWa2Ot5o4J1lkBskElgtnbxfKcrGkceEewB6OwUKrBhmuPQVsKXBkpmPjCY3HbL3md9IQRhrVRDRbccqw441gQRiQ3yjXUfLXwpxFV1VRUVFyqhZp33t5ehVCoRFeQIvSfeLkOLg3j0OXizZXFF6Y+NVqFcd+bl1/6/QaSk456K9/vnz+Ae/zVpN+mOO/GYnH7c4j4+yLvanCPkIrZHibEeQ38yLie+hy2DwmMB389ePi56KFEOzDjlGC3bN9lIURIC4zVdLTYZbwOfR9GcEP0I2oFUxUlgr6h/c9rmlVSE3U4HA7/AgAA//8DADlZ+rXpBQAA
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoxTTW/bOBD9K8KcRYUSqQ/q1sQJNkhbB7V3u9gbP0YOEYU0KCptEPi/F5Sdtm572JOE0ejNm/fevIKTTwg9vNvvr5/RxQlyiHYpVbRqCBWkbLe07nnXc1Hwpu2qlv4HOdg7fIEeWq51qQUShg0SbmhNFBs0MQZFSUtDq5onTLmboH8FaQuDz1Zj4ad/MEzWO+jhs3XGf0mzpS306GdTBD/irZuidDqRWV1v7rbre3LTNh+vLy+PnRNOCaCwBnpoBlSdkTVphdCEa6FI14qWSMkGM2CdnsffrIsYnByLyTz+4GB8dBh1XxUVKyipBBxyMDLKRFvJCbcv+8RkkWmV6vlSXl77V3jGAH2Vvwl6hLvQo714mtRsR3Mx+t3Out2Vd4PdzUHGNDiHffB7DNHiItB6k/3gVNKCFlVTUQo5fJD6wTrMblfZekwbq6E1tKIVEbJrCGeKkY5VjHBa0ZbJTjdlInmHweH4E+oHq4Of/BCzk+7Z2Zwr76J1s5+n7NZF3J2I9nAjxwlTwxwCupjdy/iQ/SWnh8TUtKbWFaIpmWqpGrDuTFd3ujVN0zWD5rWmrKw155oNygiplGC64tWgOLYN5LDF8GSdHN/73S5p+X3e+YcVDnIe48bPYUnGZB4hh0vrZHg5dvw9oTl1fTya8Ya0BGocl4Wyk59Xo0UXz/RNaSq54YIawkslCW8HQSQySVTdCN4pZhpMsb4P3sw6/moZowxy+DS7dErZbTLri3Xka8N/ofoTN0y5ypYoCymVarQgjFEkvCkrIriQhCGTkg+a8UFADiuvHzFkyS9p3RnYepO9C/rBRtRxDmnNf5fZ6012P8o4+PB0dnbrOe7nmH1CYwPqiInFNswJ6saO+BvZt71PGvIOctgcr3HZ9n8e45V3k/8D/B8dhx7kHD0cDofDNwAAAP//AwDB5+uJuAQAAA==
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTy07cMBT9lcjrODi248TZwQyoCFEQjNqqOz9uGItgjxyHh9D8e+XMIEp3XcW68jk+j5t35NUToB6d7nbnz+DThEqU3DKihApMJK7bDWl63vVcVpIwSrr2NyqRu4I31KOWG1MbCZiBAMwtabBmg8HWgqxJbQlteOZUDxPq35FylYVnZ6AK0w+Ikwse9ein8za85LeVq8wYZlvFMMKln5LyJotZn99fbW5u8UUrvp+fnR1uTjBlgspZ1CMxgO6sanArpcHcSI27VrZYKTbYAZr8PcCcTxC9GqvJPn5qsCF5SKanFWUVwVSifYmsSirL1mqCzdsuK1liWud5uYyXY/+OniGinpYfgR7oTszoTkx4elLengzOu2mLSrSLYQcxOVgiWVyOo0ou+OL4yGp04BMq0c19cTuqNIT49CWn+4P1y/9wfq3M1nkoLtcZU3PLJbGY11ph3g4SK2AK60ZI3mlmBeTaVsEn5+cwT8WlT/AQF5WoRxdqnOCg7zSarUtg0hyz9F8iA+HVpVWweUBQiW5jsLNJxWfcNalIxQjLHHPazam4A+simATZ0ybOR/p/IFRQkhlXc4zgU3Gr0rb4pqZtvmBb2xgKYGumW6IHaDrbNZ1prRCdGAxvDGF1Yzg3bNBWKq0lM5TTQXNoRdad2y2WhSINlxKswMKQBnMuNdaSMtxo0tq2YVTorP4z1uJmzDg9tJZQQrFUncCcaYY7RhnmhJKWqc6IOu/ORyTHxnmHSnQF0cP4l+VrZ2KYwpCKY/XFlxDuZp9/1mJZgxfn8esS/jqYR4hFLk85n/fy2Nd+v9//AQAA//8DAB8jFwPzAwAA
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUS2/dNhCF/4rAtSjzJT60S2IHdY3URmw0RXdDcmQL0aUuKMppYNz/XlC+gZvusiMGPMPDj4fzQhIckAzk3fF49YyprKQlZdpLgglNmaPcPLB+UHboWccsF9rwv0lLphv8TgZiVAg8OKQSNVIVWU+9HAONER1nPDLRq9oTHlcyvBCYuojPU8BuWf/EvE5LIgP5MqW4fKtnw9SFedlil5cZr9NaIIVq5vLq/ubh9o5+NPqPq/fvX3euuNYG3RTJQKKzIVghqTGipwowUDcCUAAZIcKoneOvsikVzAnmbo1f3zzEpSQsYRCdkB2jwpFTSyIUqLY9rPjw/Vid7Jgua73dy/tyeCHPmMkg2h9AX9tdhHm6KMtxxmecj5BXzBdhORwgRdKSY16OmMuEO5r9tvMMZVpScz7swzxhKqQlt/fN3QxlXPLhJ173rwiuf4HAM2ZPBiJZL7UxqKSU4LQXDKPRQWvhAzAzeoE8IOudMo4bxqUTzMleG2siMBdISx7nxcP8+7rzc4J5G5kRWqBCJ5TWLjCUFsfeIIugtdc2uCitEcpGK8EBcOfRBKdDDcknCE9Twub6kgxEcxWVY5Eq7oEqMzoKKIH6XjtlvYwaq+bDksqUtmVbm+tU8DHv/MhAPsK84iu5dzk8TQVD2XKF+peuwru8xC2U5i0BnHWsk0xW0VaOW2k+Y5wyhoIV70Pezv3+JxFaMFatbDljKs0dlKfmN1if6oZoYh8EYuTSG+ZH7G20vQ0mam31GFQfmOR9UCrI0UcH3jsZhBKjV2g0aQnWwDV7xsMYjRo9Uo9CUqUsp1b3hkouNToEiLuRN47N7Vx1fjSRCSaoA6upkl5SK2sHJpiRYIPmNc4/kJzDpyxpyQ3mhPN/rvxpCnlZl7E05xQ2P0H4vKU6P5o9kd+mRP/ZaV8u4Svmpr4WTKl+lfMDnVpyQFi3jId9/Awv5K7+k+Zhn0JCdZpz05L7Arlsx3NZ6c4xrU6n0+lfAAAA//8DAMJGy6DDBAAA
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpSUy27dNhCGX0XgWpR5v2iXxA5qGKmN+CAtuuNlFBPRoQ4oymlgnHcvKJ/ATXfdEQPO8J9vfs4Lyu4IaETvTqebZ8h1RT2qaQ8xwhQmFlN9IHIUZpRkoMRYQsVfqEfpDn6gEWkRAg0WMAcFWEQisedTwDGCpYRGwqRoNd3XFY0vyKUhwnMKMCzrFyhrWjIa0R8px+V7e9ulIczLFoeyzHCb1+pyaGKubx7vDvcP+KNWv9+8f/96c4W1FRhSRCOK1oRgGMdaM4mFg4Dt5Bx2jkcX3aSspa9pKVco2c3DGr+9aYhLzVDDyAbGB4KZReceRVddk+3dCocfp6Zkx3Td4v0e3o/jC3qGgkbW/wT6Wu4qzOlq3fwMzzCfXFmhXIXleHQ5oh6dynKCUhPsaJ6h+GVNtVENQTtjKI3GaSF1NFwyriMnEA3RMtponRJqYkHKKXpjtZhEZFREBUJ4jXq0w5tnV9OSu4v2D3OCXFGP7h+7h9nVaSnHX/A/vhK9/R9Am2w0Ik4kV1qD4Jw7qzwjELUKSjEfHNGTZ0ADEGmFtlQTyi0jlkuljY6O2IB69MmFp5Shu71GI1JURGFJxIJ6h4WeLHbAHfZSWWE8b42iHn1Yck15W7a1u80Vvpa9XzSij25e4bXTdyU8pQqhbqVB+FO1xIeyxC3U7s0AlAxk4IS3pK2ettp9hpgKhAoNx6Fsl3r/SWGKEdKkbKVArt2Dq0/db259aheijjIwgEi518RPIE000gQdlTJqCkIGwqkMQgQ++Wid95YHJtjkBWiFegTNb91u8TBJyi112BhtsIheYi8Nw0QqYo0xOoo2kTeO3f3c8vykI2GEYeuMwoJ7jg1nHAvCiObOBEWbm38iuZhFGNSjOygZ5n+1/CmFsqzLVLuLa7pfIHzeclsf3e6g7ynjv3fa10v4BqVr03Ipt59yGdC5R0dw61bguG+f8QU9tG/SHfYlxMSgKNU9eqyu1O10CQs1WKLE+Xw+/wMAAP//AwC8ws6OwgQAAA==
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoyUTW/bOBCG/4owZ1GhSOrz1sQJNsi2Tmu3u9gbRY4cIgppkFS6QeD/vqDsbOp2D3sTRqN3nnlnRq9g5RNCDx/2++tntDFADtEsIUZZTWhHymZLq160fUWLsmmrqhJ/QQ7mDl+gh0YoVaoOCccaidC0IgMfFdEau5KWmrJKJE25C9C/gjSFxmejsHDhG/pgnIUe/jBWu++ptjSFmtysC+8mvLUhSqsSzOp6c7dd35Obpv50fXl5zAwYkkBhNPSgu1aplnHSNKwiQqIi3SglkZJrqeVYd115/MzYiN7KqQj68Z1Bu2gxqp4VjBeUsA4OOWgZZcIeZMDtyz6RLDatUjxfwstj/wrP6KFn+ZuhR7kLNZmLpzDMZtIXk9vtjN1dOTua3exlTIVz2Hu3Rx8NLgatN9k7U0kLWrCaUQo5fJTqwVjMblfZekodD2OjKaOMdLKtieADJy1nnAjKaMNlq+oyQd6htzj9oPrRKO+CG2N28j07q3PlbDR2dnPIbm3E3Qm0hxs5BUwJs/doY3Yv40P2mwwPiVQ3ulIMUZd8aOgwYtXqtmpVo+u6rUclKkV5WSkhFB8H3clh6Lhigo2DwKaGHLbon4yV0+9ut0te/lvv/MUKRzlPceNmv2xG0I+Qw6Wx0r8cM74G1KesT8dhvCktCzVNS0PZaZ5Xk0Ebz/yFHupSaNFRTUQ5SCKasSMSuSRDVXeiHbiuMa31vXd6VvHnkXHKIYcvs02nlN2mYX03lvxdi59Qf2DDtFfZssp1Q5EPoyZIO0GEGJFIHCnRVOMoq6qsaAc5rJx6RJ+leUljz8TWm+yDVw8mooqzT23+udReb7L7ScbR+aezs1vPcT/H7Atq41FFTBRbPyepGzPhL7BvfZ88FO1xb4J7y/2GfnDBxPSL+DwbTAZvjue62PE/r/VM853pPzcCepBzdHA4HA7/AAAA//8DAL7u70zYBAAA
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACoxUy27jOBD8FYFnUaFI6mVgDxknwQbZjI3Ys7vYGx/NmIhMGhSVTBDk3weU5YkzmMPeCLIfVdXVfENO7AEt0OXhcP0MLg4oR9FOV5TQGpMOl82WVAveLipS1C1rSMn/Qzmyd/CKFqjhSpWqA8ygBsw1qbBkRmGtoStJqQmteKopHge0eEPCFhqerYLCD39DGKx3aIH+sU77l9Rb2EL1ftRF8D3cuiEKpxKYq+vN3Xa1xjdN/fX6y5dj5ABDKlBYjRZId61SLWW4aWiFuQCFOyMEFoJpoYWpu648plkXITjRF4N++sCgfXQQ1YIWlBUE0w6950iLKBJsKQbYvh4Skkmmq3SfT9fTcfGGniGgBc1Pgh7LXajeXuwHOdpeX0QRHiGaIPbw4sMTPIse5egQ/AFCtDDps9pkH5BKUpCC1pQQlKN7oXbWQXZ7la36RFiaRhNKKO5EW2POJMMtowxzQknDRKvqMmG8g+CgP6t6b1Xwgzcxm2XPPvVZehetG/04ZLcuwmMQ8Zh3I/oBUI7Wo+ztsHsAoV+3/mFMb27sE5flGAK4mK1F3GV/imGXSOhGV4oC6JLJhkgDVavbqlWNruu2NopXirCyUpwrZqTuhJQdU5RTIzk0NcrRaoyHMZ70/34GYhvsfg/6A8F20njdi2h82N9qcNEam0ZziphM1fcTq2yuuewtuPhJZLRAdck174jGvJQC88Z0WAATWFZ1x1vJdA3J2kvvjH0cf+pEjNGcmapta9nJkgA1TctJYyTRyjDV6UpCQxvStNB2ihBZ8bKVnSIayorrxC54Par4qxUYYShHD6NLG5rdJtov1uHvNf9QZB189Mr3aWC2h83rEGH/u6kt/f7gBxsTfzOP9tsA996l1/kPmDWbky99PAuGtAjZtHtJFg2c4k7VDHNtGiwp0biuW97UXDDQHcrRlVdPELLkMGHdNJSTqVab7DKonY2g4hhS638nVsd53px25kOQ4uv1dukDXB4O+Xz7x3NXJAevNtnJAJ/+lm9Dit75IbGIYTzz0QZ6c4J15qaj8bIH0DaAitPT9pg4a/Q7h12GaI1QcUhr8JdXkzFmp80hv/Y7G8DP7GPzsyHMppgr8XaqM/2AkxX+1wf4/v7+/gMAAP//AwCxdSWE9QUAAA==
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTTU/cMBD9K9Gc4+DYjvNxoyxVEWpBsGqr3ib2pFgEZ+U4fAjtf6+cXUTprRfLGvnNvPfm+RU8PhB0cLrbnT+SjzPkEN1aElxoxltW1ltedarpKl00Uopa6l+Qg7ukF+igVsaUpiUmSRNTllesl4Nh1lJb8tJyUanUE3/P0L0CusLSozNUTPN3CrObPHTww3k7PaXZ6AozTostwjTShZ8jepPIbM5vL7dX1+xzrb+df/p0eDnTnBoUzkIHtm2MaYRkdS0qppAMawdEhigtWhx025YHmPORgsexmO39Owc7RU/RdKIQsuBMtLDPwWLERLvHmbYvu8RktWmT6vlaXq/dKzxSgE7kb4Ye2p2Y0Z08zP3iRntyOCPO90npLkw7CtHR6ssqdRwxuslnx0lnoyMfIYer2+x6xDhM4eGDWbcH/Rf/IX+bpp8/k1ki2bNp8RE6UAJy+IrmznnKLjbQgS6VVS23TJU9MlUPLUOSyPpKt6rppdWUtno2+ej8Mi1zduEj/Q4rf+jgM44zHZifBnPnIpm4hCTqp07A6zDZxcTs3f6SF7yQXCbQEndLzG7IukAmUpK3Dcux3z8QoQXnicoSAvmYXWO8y77gfJce2NpWRhDZUvY17weqGttUjamt1o0ejKoMl2VllDJy6G2Lfd9KI5QYekW1hhwobTtbA9YS15wImUbNmdKVYNhzYkZz5FXbN7ZMmXj3MbsaE64fassFF6zFRjMle8kaKSRTXPBaYmP0inuz5Lh81UAOlxQ8jX9J/upMmOZpiNkxBdkHE24Wnz5vtibiyXn2vLq9mcw9hSxtC51POT0uaL/f7/8AAAD//wMAd4cViwMEAAA=
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRTXW/bIBT9KxbPwcWAMfitbVotqrpGbbRNe8Nw3aA6EGHcD1X57xNOqq5725t1xTn3fFy/I693gFp0vt9fPYNPI1qg5OYRJVRgonDVbEjdctnWopSMM0bVb7RA7gbeUIsabkxlFGAGAjC3pMYd6w22FlRFKktozTOnfhxR+460Ky08OwNlGH9AHF3wqEU/nbfhJe/WrjRDmGwZwwArPybtTRazvHq42dyt8XUjvl9dXBxfjjBmgtJZ1CKrpDGSMtw0tMZcg8Gq1xprzay2uhdKVUeY8wmi10M52qdPDTYkD8m0tKSsJJgqdFggq5POsjs9wuZtn5XMMS3zfDGP58/2HT1DRC1dfAR6pDszgzszYbfT3p71zrtxixZoH8MeYnIwRzK7HAadXPDFacnl4MAntEB3D8V60KkPcfclp4ej9dV/OL/VZus8FKslapGouOWKWMyrTmPe9AprYBp3tVBcdswKyLVdBp+cn8I0Fiuf4DHOKlGLrvUwwlHfeTRbl8CkKWbpv0QGwqtLl8HmQd69jsFOJhWfcVekJCUjLHNMaT+l4h6si2ASZE+bOJ3o/4FQQQnJyqYYwadirdO2+KbHbX5gG1sbCmAr1jWk66GWVtbSNFYIKXrDa0NYVRvODes7q3TXKWYop33HoRFZd263mA+qMY1QnQbMOasxZ12HOwUWV5KQCqSESvdfYi3uhozr+sYSSihWWoqMY1gyyjAnlDRMSyOqfDsfkZwa5xIt0A1ED8Nflm+diWEMfSpO1RdfQriffP5Zi/kMXpzHr3P4y2CeIBa5PO18vstTX4fD4fAHAAD//wMA/WXCnPMDAAA=
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRUy27kIBD8FYvz4GDA2PiWpzaKsok2o+xqbzzaCYoDI8B5KJp/X+GZKDun1V4QancX1VUlfyCvngEN6HizOX8BnxNaoeyWEiVUYCJx061JO/B+aEXdU0lb2v1GK+Su4B0NqOPGNEYCZiAAc0tarNlosLUgG9JYQlteMNVDQsMHUq628OIM1CHdQ0wueDSgn87b8FreVq42U5htHcMElz5l5U0hc3Z+d7W+ucUXnfh+fnKy60yQCkDtLBqQlb0xPWW462iLuQKD5agUVopZZdUopGx2Y85niF5NdbJPXxxsyB6yGWhNWU0wlWi7QlZlVWhrlWD9vilMFpnOSn21lJfr8IFeIKKBrj4F3cEdmckdPSc9u8ke7c6s0lNazlGZHOI7WqFNDBuI2cGi0XFK8Kyn97VKTxe7nnJN529g5gz2NMw+owExgVZo0WiaVHbBV3uKp5MDn5ePOTqfnPkXVEG6uatuJ5XHEJ8PLLnbqXz5HyJfK/PoPFSXZwW74ZZLYjFvtMK8GyVWwBTWrZC818wKKAk5DT47P4c5VZc+w0NcVkIDulBTgh2/42geXQaT51j2/CXK4G0Mdja5+rKyITWpGWFlaM6bOVc/wLoIJkNZYh3nPd794QgVlJBCZY4RfK5uVX6svqn0WBpsZ1tDAWzDdEf0CG1v+7Y3nRWiF6PhrSGsaQ3nho3aSqW1ZIZyOmoOXREYSnKqJaySso522mA7tgJzYhusjBW4oVJT23R2pPRAx+pmKnN67CyhhGKpeoE50wz3jDLMCSUdU70RTcnlpyT7PPAerdAVRA/TXytfOxNDCmOu9l5XByL8mH35EVSL76/O47dF7bNgniBWxS3lfMn83qDtdrv9AwAA//8DAJ9uh65PBAAA
|
||||
@@ -0,0 +1,5 @@
|
||||
https://dc.services.visualstudio.com/v2/track
|
||||
Content-Type:application/x-json-stream
|
||||
Content-Encoding:gzip
|
||||
|
||||
H4sIAAAAAAAACpRVTW/jNhD9KwKvlbQSKZGSgD1kHWcbLPKB2OgWvQ3JYcxGIQ2KyjYN/N8LyknX2UUPvdjSSDPz5r031Atx8IhkIGf7/foJXZxITqJdQrSivKj6ohbbqh2abmh52bGu5XX/B8mJ/YLPZCCiUapWPRYMORaNrtpCMqMKrbGvq1pXtG1STbifyPBCwJYan6zC0k+/YZisd2QgX63T/lvqDbZUo591GfyIl26K4FQCc77efNne3BYXgl+vP306vjnhlAqUVpOB6L5TqqOsEIK2RQOoit4AFABMgwbD+74+plkXMTgYy0k/fMegfXQY1UBLysqqoD055ERDhARbwoTb531CstB0nuL5El4uhxfyhIEMNH8j9Fjugxrth8dJznbUH5ZfkpN98HsM0eLCyDLkOEK03mWvPVajRRdJTj6llM2sFE4TGcgFjBO+hdfu3jq8CPCI33x4uD72La/XW5KT2+D/RBVvIe7IQJhRnTZS1dD00kjUPa9awB5MI5vOaM1qZRStTcWFkLQxrNNSAuei1VJSSnJys8luR4jGh8d3gm2OGlz+Dwk2Z6u1Azmi/nGkLYR7jGQgLW8lMtNRLjlo6FlnakDWayUZb7GTQncoBKMMVKuqrla1qFvoRdsKzmjqcjltIjgNo3e4/gvVnAgmA9mGOfW7ArWzDrPLczIQXje66StdNLWEohGmLwAZFLLlfdNJpjkmE1+AHeeAK4h470Ny//X8GZNOK++idbOfp+zSRbwPi5wn491ssrOgdjaiinNIQv3OU8nb4PWsYvbdh3VVViWrWEqa436O2R1qG1DFha9X+DebH1Mop1WVoMwhoItZkj77Faakf62FbhVF1DWTopIG2053baeE5rzjRjWtqljdqqZRzEjdg5Q9U7ShRjYoOMkJJttny6Y1XS8qo6DoIFHGelOAkrLg0vTQiKpjlXjHcHYzpjxphK5oRYseOl40TLKiY5QVTUUrwaBTvIb35j4ZsSt5WTclE91CzRtvrwvTdCQn6xB8WPk5nWLD6d2lMz57yVb+cW9HDNnHLM+upqXPZ3QYYDwNrZ9gnI/reBp9s9C77AD73RLYwvSwXGz0wx1OfnzCMC2Ba4wbfXy2uCX7mNV5tqSvdqhen0C0T7j1flQ7sMceK6/xzMH4PNljpTv42x/Bf92b5f9s2l8vFfPsJu6WybIDyckXDA7HE4dcWRX85E3MXjc3e+eZE8rP7bQf4fkn5n9BwSpTa5mk/Y7+50W+m136gGTLifDNuuKvxejnXj1gyNKigHXprPw342oeo93uAoJGfeU1/lj0kJNHhGkO+Lh8opZT02FYYJzPx2W7dFd2HO2Eyjs9kYFz3pSM0/YV7n++JxpaVqzmh8Ph8A8AAAD//wMADw4oQRAHAAA=
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"Microsoft.NET.Workload.Emscripten.Current": "10.0.111/10.0.100",
|
||||
"Microsoft.NET.Workload.Emscripten.net6": "10.0.111/10.0.100",
|
||||
"Microsoft.NET.Workload.Emscripten.net7": "10.0.111/10.0.100",
|
||||
"Microsoft.NET.Workload.Emscripten.net8": "10.0.111/10.0.100",
|
||||
"Microsoft.NET.Workload.Emscripten.net9": "10.0.111/10.0.100",
|
||||
"Microsoft.NET.Sdk.Android": "36.1.69/10.0.100",
|
||||
"Microsoft.NET.Sdk.iOS": "26.5.10315/10.0.100",
|
||||
"Microsoft.NET.Sdk.MacCatalyst": "26.5.10315/10.0.100",
|
||||
"Microsoft.NET.Sdk.macOS": "26.5.10315/10.0.100",
|
||||
"Microsoft.NET.Sdk.Maui": "10.0.20/10.0.100",
|
||||
"Microsoft.NET.Sdk.tvOS": "26.5.10315/10.0.100",
|
||||
"Microsoft.NET.Workload.Mono.ToolChain.Current": "10.0.111/10.0.100",
|
||||
"Microsoft.NET.Workload.Mono.ToolChain.net6": "10.0.111/10.0.100",
|
||||
"Microsoft.NET.Workload.Mono.ToolChain.net7": "10.0.111/10.0.100",
|
||||
"Microsoft.NET.Workload.Mono.ToolChain.net8": "10.0.111/10.0.100",
|
||||
"Microsoft.NET.Workload.Mono.ToolChain.net9": "10.0.111/10.0.100"
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
10.0.300
|
||||
+1
@@ -0,0 +1 @@
|
||||
10.0.303.1
|
||||
@@ -13,41 +13,16 @@ namespace Distribution.Migrations
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_DeliveryRuns_Routes_FK_RouteId",
|
||||
table: "DeliveryRuns");
|
||||
DropForeignKeyIfExists(migrationBuilder, "DeliveryRuns", "FK_DeliveryRuns_Routes_FK_RouteId");
|
||||
DropForeignKeyIfExists(migrationBuilder, "DeliveryRuns", "FK_DeliveryRuns_Warehouses_FK_SourceWarehouseId");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_DeliveryRuns_Warehouses_FK_SourceWarehouseId",
|
||||
table: "DeliveryRuns");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_DeliveryTasks_Orders_FK_OrderId",
|
||||
table: "DeliveryTasks");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_LoadingSheetItems_LoadingSheets_FK_LoadingSheetId",
|
||||
table: "LoadingSheetItems");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_LoadingSheets_Warehouses_FK_SourceWarehouseId",
|
||||
table: "LoadingSheets");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Payments_Sales_FK_SaleId",
|
||||
table: "Payments");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_Payments_Stores_FK_StoreId",
|
||||
table: "Payments");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_SaleReturns_Orders_FK_OrderId",
|
||||
table: "SaleReturns");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_SchemeRedemptions_Orders_FK_OrderId",
|
||||
table: "SchemeRedemptions");
|
||||
DropForeignKeyIfExists(migrationBuilder, "DeliveryTasks", "FK_DeliveryTasks_Orders_FK_OrderId");
|
||||
DropForeignKeyIfExists(migrationBuilder, "LoadingSheetItems", "FK_LoadingSheetItems_LoadingSheets_FK_LoadingSheetId");
|
||||
DropForeignKeyIfExists(migrationBuilder, "LoadingSheets", "FK_LoadingSheets_Warehouses_FK_SourceWarehouseId");
|
||||
DropForeignKeyIfExists(migrationBuilder, "Payments", "FK_Payments_Sales_FK_SaleId");
|
||||
DropForeignKeyIfExists(migrationBuilder, "Payments", "FK_Payments_Stores_FK_StoreId");
|
||||
DropForeignKeyIfExists(migrationBuilder, "SaleReturns", "FK_SaleReturns_Orders_FK_OrderId");
|
||||
DropForeignKeyIfExists(migrationBuilder, "SchemeRedemptions", "FK_SchemeRedemptions_Orders_FK_OrderId");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "OrderItems");
|
||||
@@ -58,57 +33,20 @@ namespace Distribution.Migrations
|
||||
migrationBuilder.DropTable(
|
||||
name: "Sales");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Payments_FK_SaleId",
|
||||
table: "Payments");
|
||||
DropIndexIfExists(migrationBuilder, "Payments", "IX_Payments_FK_SaleId");
|
||||
DropIndexIfExists(migrationBuilder, "LoadingSheets", "IX_LoadingSheets_FK_SourceWarehouseId");
|
||||
DropIndexIfExists(migrationBuilder, "DeliveryRuns", "IX_DeliveryRuns_FK_RouteId");
|
||||
DropIndexIfExists(migrationBuilder, "DeliveryRuns", "IX_DeliveryRuns_FK_SourceWarehouseId");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_LoadingSheets_FK_SourceWarehouseId",
|
||||
table: "LoadingSheets");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_DeliveryRuns_FK_RouteId",
|
||||
table: "DeliveryRuns");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_DeliveryRuns_FK_SourceWarehouseId",
|
||||
table: "DeliveryRuns");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FK_SaleId",
|
||||
table: "Payments");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DispatchConfirmedAt",
|
||||
table: "LoadingSheets");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DispatchConfirmedBy",
|
||||
table: "LoadingSheets");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "QuantityReceived",
|
||||
table: "LoadingSheetItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "QuantityResolved",
|
||||
table: "LoadingSheetItems");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ActivatedAt",
|
||||
table: "DeliveryRuns");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FK_RouteId",
|
||||
table: "DeliveryRuns");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FK_SourceWarehouseId",
|
||||
table: "DeliveryRuns");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "RideType",
|
||||
table: "DeliveryRuns");
|
||||
DropColumnIfExists(migrationBuilder, "Payments", "FK_SaleId");
|
||||
DropColumnIfExists(migrationBuilder, "LoadingSheets", "DispatchConfirmedAt");
|
||||
DropColumnIfExists(migrationBuilder, "LoadingSheets", "DispatchConfirmedBy");
|
||||
DropColumnIfExists(migrationBuilder, "LoadingSheetItems", "QuantityReceived");
|
||||
DropColumnIfExists(migrationBuilder, "LoadingSheetItems", "QuantityResolved");
|
||||
DropColumnIfExists(migrationBuilder, "DeliveryRuns", "ActivatedAt");
|
||||
DropColumnIfExists(migrationBuilder, "DeliveryRuns", "FK_RouteId");
|
||||
DropColumnIfExists(migrationBuilder, "DeliveryRuns", "FK_SourceWarehouseId");
|
||||
DropColumnIfExists(migrationBuilder, "DeliveryRuns", "RideType");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "FK_OrderId",
|
||||
@@ -957,5 +895,66 @@ namespace Distribution.Migrations
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
}
|
||||
|
||||
private static void DropForeignKeyIfExists(MigrationBuilder migrationBuilder, string tableName, string constraintName)
|
||||
{
|
||||
migrationBuilder.Sql($"""
|
||||
SET @sql = IF(
|
||||
EXISTS(
|
||||
SELECT 1
|
||||
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
|
||||
WHERE CONSTRAINT_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = '{tableName}'
|
||||
AND CONSTRAINT_NAME = '{constraintName}'
|
||||
AND CONSTRAINT_TYPE = 'FOREIGN KEY'
|
||||
),
|
||||
'ALTER TABLE `{tableName}` DROP FOREIGN KEY `{constraintName}`',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
""");
|
||||
}
|
||||
|
||||
private static void DropIndexIfExists(MigrationBuilder migrationBuilder, string tableName, string indexName)
|
||||
{
|
||||
migrationBuilder.Sql($"""
|
||||
SET @sql = IF(
|
||||
EXISTS(
|
||||
SELECT 1
|
||||
FROM INFORMATION_SCHEMA.STATISTICS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = '{tableName}'
|
||||
AND INDEX_NAME = '{indexName}'
|
||||
),
|
||||
'DROP INDEX `{indexName}` ON `{tableName}`',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
""");
|
||||
}
|
||||
|
||||
private static void DropColumnIfExists(MigrationBuilder migrationBuilder, string tableName, string columnName)
|
||||
{
|
||||
migrationBuilder.Sql($"""
|
||||
SET @sql = IF(
|
||||
EXISTS(
|
||||
SELECT 1
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = '{tableName}'
|
||||
AND COLUMN_NAME = '{columnName}'
|
||||
),
|
||||
'ALTER TABLE `{tableName}` DROP COLUMN `{columnName}`',
|
||||
'SELECT 1'
|
||||
);
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
""");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"Urls": "http://localhost:5089",
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Server=187.127.102.190;Port=3306;Database=distribution_db;User=kalana;Password=kalana@hexdive"
|
||||
"DefaultConnection": "Server=187.127.102.190;Port=3306;Database=distribution;User=kalana;Password=kalana@hexdive"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
|
||||
@@ -684,15 +684,33 @@ appsettings.Development.json
|
||||
|
||||
---
|
||||
|
||||
## Open Items (must resolve before coding the relevant phase)
|
||||
## Open Items
|
||||
|
||||
### Resolved during implementation
|
||||
|
||||
| # | Item | Resolution |
|
||||
|---|---|---|
|
||||
| 2 | Cascade when Confirmed ShopPO is cancelled and Invoice already exists | Cancel is rejected (400) if a linked Invoice exists. Invoice must be voided separately before the ShopPO can be cancelled. No automatic cascade. |
|
||||
| 3 | Cascade when Confirmed ShopPO is cancelled and it's in a LoadingSheet | Cancel is rejected (400) if the ShopPO appears in any Planned LoadingSheet. Operator must remove it from the sheet first. |
|
||||
| 4 | `FinalizeInvoiceRequest` payload — free issues field shape | Deferred to a later pass. v1 `POST /api/invoices/{id}/finalize` takes no body and locks the invoice as-is. |
|
||||
| 5 | Who can confirm a ShopPO? | No role restriction in v1 — any user can confirm. Gating deferred until the auth layer is implemented. |
|
||||
|
||||
### Still open
|
||||
|
||||
| # | Item | Blocks |
|
||||
|---|---|---|
|
||||
| 1 | Who holds `loading.confirm` permission? | Phase 9 — ConfirmAsync auth guard |
|
||||
| 2 | Cascade when Confirmed ShopPO is cancelled and Invoice already exists | Phase 7 — CancelAsync |
|
||||
| 3 | Cascade when Confirmed ShopPO is cancelled and it's in a LoadingSheet | Phase 7 — CancelAsync |
|
||||
| 4 | `FinalizeInvoiceRequest` payload — free issues field shape | Phase 6 — FinalizeAsync |
|
||||
| 5 | Who can confirm a ShopPO? (agent, office, either?) | Phase 7 — ConfirmAsync permission |
|
||||
| 1 | Who holds `loading.confirm` permission? | Permission seeding (`loading.populate`, `loading.confirm`) — explicitly noted as **Pending** in the API reference. Must decide role before seeding. |
|
||||
|
||||
### New items — identified during frontend refactor (2026-09-17)
|
||||
|
||||
| # | Item | Area |
|
||||
|---|---|---|
|
||||
| 6 | `order-return-edit-dialog.tsx` — file not verified; client imports it but it was not reviewed during the refactor pass. Needs to be checked for old `orderId`/`OrderResponse` references. | Returns |
|
||||
| 7 | `sales-visits/` components — any components in this section that interact with sales/orders need auditing against the new ShopPO/Invoice types. | Sales Visits |
|
||||
| 8 | SysConfig management page not built — the backend has a `SysConfig` model and the `InvoiceCreationMode` config key is read by `ShopPOController`. A UI to view and toggle system config is missing. | Config |
|
||||
| 9 | InvoiceCreationMode (ModeA vs ModeB) UI branching — the ShopPO confirm button calls `confirm` or `confirmAndCreateInvoice` depending on mode, but the mode is not currently read from the backend. The UI should fetch `GET /api/sys-config` and gate the action accordingly. | Sales / Invoice |
|
||||
| 10 | Loading Sheet item population UI — `PUT /api/loading-sheets/{id}/items` exists in the API layer but no UI for populating/editing items on a Planned sheet has been built. | Dispatch |
|
||||
| 11 | `PaymentMethod` enum is missing `Cheque` — per `invoice_collection.md`, the final design includes `Cash`, `Cheque`, `Credit`. The frontend only has `Cash` and `Credit`. Cheque-specific fields (`ChequeNumber`, `ChequeDate`) and `EntryMode` (`RealTime`, `DayEndManual`) are also unimplemented. | Payments |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,978 @@
|
||||
# Frontend API Reference — ERP Distribution (Post-Refactor)
|
||||
|
||||
> Last updated: 2026-09-17
|
||||
> Base URL: `https://<host>/api`
|
||||
> All responses are JSON. Enum fields are serialized as **strings** (e.g. `"Pending"`, `"ModeB"`).
|
||||
> All `Guid` fields are UUID v4 strings. All `DateTime` fields are ISO 8601.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Pagination](#pagination)
|
||||
2. [Enums Reference](#enums-reference)
|
||||
3. [ShopPO](#shoppo)
|
||||
4. [Invoice](#invoice)
|
||||
5. [Payment](#payment)
|
||||
6. [Ride (DeliveryRun)](#ride-deliveryrun)
|
||||
7. [Loading Sheet](#loading-sheet)
|
||||
8. [Delivery Tasks](#delivery-tasks)
|
||||
9. [Returns](#returns)
|
||||
10. [Schemes](#schemes)
|
||||
11. [System Config](#system-config)
|
||||
12. [Key Relationships & Flows](#key-relationships--flows)
|
||||
|
||||
---
|
||||
|
||||
## Pagination
|
||||
|
||||
All list endpoints return a `PagedResponse<T>`:
|
||||
|
||||
```json
|
||||
{
|
||||
"page": 1,
|
||||
"pageSize": 20,
|
||||
"total": 84,
|
||||
"items": [ /* T[] */ ]
|
||||
}
|
||||
```
|
||||
|
||||
Default page size is `20`. Pass `?page=2&pageSize=50` to paginate.
|
||||
|
||||
---
|
||||
|
||||
## Enums Reference
|
||||
|
||||
| Enum | Values |
|
||||
|------|--------|
|
||||
| `ShopPOStatus` | `Pending`, `PartiallyFulfilled`, `Fulfilled`, `Cancelled` |
|
||||
| `InvoiceStatus` | `Draft`, `Printed`, `Finalized` |
|
||||
| `PaymentStatus` | `Pending`, `PartiallyPaid`, `Paid` |
|
||||
| `PaymentMethod` | `Cash`, `Credit` |
|
||||
| `RideStatus` | `Open`, `Cancelled`, `Closed` |
|
||||
| `LoadingSheetStatus` | `Planned`, `Confirmed` |
|
||||
| `DeliveryTaskStatus` | `Pending`, `Delivered`, `PartiallyDelivered`, `Failed` |
|
||||
| `ReturnMode` | `CreditDeduction`, `ItemExchange` |
|
||||
| `ReturnReasonCode` | `Expired`, `Damaged`, `OrderError`, `Other` |
|
||||
| `CancelRequestType` | `VehicleBreakdown`, `DriverUnavailable`, `RouteIssue`, `Other` |
|
||||
| `InvoiceCreationMode` | `ModeA`, `ModeB` |
|
||||
| `SchemeRuleType` | `SlabDiscount`, `FlatPercent`, `BuyXGetY`, `FreeBundle` |
|
||||
|
||||
---
|
||||
|
||||
## ShopPO
|
||||
|
||||
A ShopPO is a shop-facing purchase order raised by a sales agent on behalf of a store.
|
||||
|
||||
### Status Flow
|
||||
|
||||
```
|
||||
Pending → PartiallyFulfilled → Fulfilled
|
||||
↘ Cancelled
|
||||
```
|
||||
|
||||
### `GET /api/shop-pos`
|
||||
|
||||
List all ShopPOs with optional filters.
|
||||
|
||||
**Query params:**
|
||||
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `agentId` | `Guid?` | Filter by the agent who raised the PO |
|
||||
| `routeId` | `Guid?` | Filter by the store's route |
|
||||
| `shopId` | `Guid?` | Filter by store |
|
||||
| `confirmed` | `bool?` | `true` = confirmed only, `false` = unconfirmed only |
|
||||
| `page` | `int` | Default `1` |
|
||||
| `pageSize` | `int` | Default `20` |
|
||||
|
||||
**Response:** `PagedResponse<ShopPOResponse>`
|
||||
|
||||
```json
|
||||
{
|
||||
"page": 1,
|
||||
"pageSize": 20,
|
||||
"total": 12,
|
||||
"items": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"storeId": "uuid",
|
||||
"storeName": "Colombo Mart",
|
||||
"raisedById": "uuid",
|
||||
"invoiceId": "uuid | null",
|
||||
"totalAmount": 15000.00,
|
||||
"status": "Pending",
|
||||
"confirmedAt": "2026-09-17T10:00:00 | null",
|
||||
"confirmedBy": "uuid | null",
|
||||
"fulfilledAt": "2026-09-17T14:00:00 | null",
|
||||
"items": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"itemId": "uuid",
|
||||
"itemName": "Milo 400g",
|
||||
"quantity": 10,
|
||||
"quantityFulfilled": 0,
|
||||
"unitPrice": 850.00,
|
||||
"isActive": true
|
||||
}
|
||||
],
|
||||
"isActive": true,
|
||||
"createdAt": "2026-09-17T08:00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/shop-pos/{id}`
|
||||
|
||||
**Response:** `ShopPOResponse` (same shape as above, single object) or `404`.
|
||||
|
||||
---
|
||||
|
||||
## Invoice
|
||||
|
||||
An Invoice is the billing document generated from a confirmed ShopPO. There is at most one Invoice per ShopPO.
|
||||
|
||||
**InvoiceCreationMode** (system config) controls whether the invoice is:
|
||||
- **ModeA** — auto-created when the ShopPO is confirmed
|
||||
- **ModeB** — created as a separate explicit step (default)
|
||||
|
||||
### Status Flow
|
||||
|
||||
```
|
||||
Draft → Printed → Finalized
|
||||
```
|
||||
|
||||
Once Finalized, the invoice is locked. Payments can be collected at any status.
|
||||
|
||||
### `GET /api/invoices`
|
||||
|
||||
**Query params:**
|
||||
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `agentId` | `Guid?` | Agent who raised the ShopPO |
|
||||
| `routeId` | `Guid?` | Store's route |
|
||||
| `shopId` | `Guid?` | Store |
|
||||
| `status` | `InvoiceStatus?` | `Draft`, `Printed`, `Finalized` |
|
||||
| `page` | `int` | Default `1` |
|
||||
| `pageSize` | `int` | Default `20` |
|
||||
|
||||
**Response:** `PagedResponse<InvoiceResponse>`
|
||||
|
||||
```json
|
||||
{
|
||||
"page": 1,
|
||||
"pageSize": 20,
|
||||
"total": 5,
|
||||
"items": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"shopPOId": "uuid",
|
||||
"storeName": "Colombo Mart",
|
||||
"status": "Draft",
|
||||
"paymentStatus": "Pending",
|
||||
"createdBy": "uuid",
|
||||
"printedAt": null,
|
||||
"printedBy": null,
|
||||
"finalizedAt": null,
|
||||
"finalizedBy": null,
|
||||
"totalAmount": 15000.00,
|
||||
"isActive": true,
|
||||
"createdAt": "2026-09-17T08:30:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/invoices/{id}`
|
||||
|
||||
**Response:** `InvoiceResponse` (single) or `404`.
|
||||
|
||||
> **Note:** The detail response from the repository also loads `Payments`. The current `InvoiceResponse` DTO does not yet expose payments — use `GET /api/invoices/{id}/payments` for that.
|
||||
|
||||
---
|
||||
|
||||
## Payment
|
||||
|
||||
Payments are collected against an Invoice. Multiple payments can be made on one invoice (e.g. partial cash collection).
|
||||
|
||||
### `GET /api/invoices/{invoiceId}/payments`
|
||||
|
||||
Returns all payments for the invoice.
|
||||
|
||||
**Response:** `PaymentResponse[]`
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "uuid",
|
||||
"invoiceId": "uuid",
|
||||
"amount": 5000.00,
|
||||
"method": "Cash",
|
||||
"collectedAt": "2026-09-17T15:00:00",
|
||||
"collectedBy": "uuid",
|
||||
"createdAt": "2026-09-17T15:00:00"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Returns `404` if the invoice does not exist.
|
||||
|
||||
### `POST /api/invoices/{invoiceId}/payments`
|
||||
|
||||
Record a payment against an invoice.
|
||||
|
||||
**Request body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"amount": 5000.00,
|
||||
"method": "Cash",
|
||||
"collectedByUserId": "uuid | null"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Notes |
|
||||
|-------|------|----------|-------|
|
||||
| `amount` | `decimal` | Yes | Must be > 0 |
|
||||
| `method` | `PaymentMethod` | Yes | `Cash` or `Credit` |
|
||||
| `collectedByUserId` | `Guid?` | No | Defaults to authenticated user |
|
||||
|
||||
**Response:** `PaymentResponse` or `404` if invoice not found, `400` on business rule violation.
|
||||
|
||||
---
|
||||
|
||||
## Ride (DeliveryRun)
|
||||
|
||||
A Ride represents a vehicle dispatch for a day. The driver moves along a route making deliveries.
|
||||
|
||||
### Status Flow
|
||||
|
||||
```
|
||||
Open → Closed
|
||||
↘ Cancelled
|
||||
```
|
||||
|
||||
### `GET /api/rides`
|
||||
|
||||
**Query params:**
|
||||
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `status` | `RideStatus?` | `Open`, `Cancelled`, `Closed` |
|
||||
| `driverProfileId` | `Guid?` | Filter by driver |
|
||||
| `vehicleId` | `Guid?` | Filter by vehicle |
|
||||
| `page` | `int` | Default `1` |
|
||||
| `pageSize` | `int` | Default `20` |
|
||||
|
||||
**Response:** `PagedResponse<RideResponse>`
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"status": "Open",
|
||||
"vehicleId": "uuid",
|
||||
"vehicleNumberPlate": "WP CAB 1234",
|
||||
"driverProfileId": "uuid",
|
||||
"driverFullName": "Kamal Perera",
|
||||
"meterstart": 12450,
|
||||
"meterend": 0,
|
||||
"startedAt": null,
|
||||
"closedAt": null,
|
||||
"createdAt": "2026-09-17T06:00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/rides/{id}`
|
||||
|
||||
**Response:** `RideResponse` or `404`.
|
||||
|
||||
### `POST /api/rides`
|
||||
|
||||
Create a new ride.
|
||||
|
||||
**Request body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"vehicleId": "uuid",
|
||||
"driverProfileId": "uuid",
|
||||
"meterstart": 12450
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** `201 Created` with `RideResponse`. `400` on validation error.
|
||||
|
||||
### `PUT /api/rides/{id}/driver`
|
||||
|
||||
Re-assign the driver on an Open ride.
|
||||
|
||||
**Request body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"driverProfileId": "uuid"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** `RideResponse`. `404` if ride not found. `400` if ride is not Open.
|
||||
|
||||
### `POST /api/rides/{id}/close`
|
||||
|
||||
Close a ride and record the ending odometer.
|
||||
|
||||
**Request body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"meterend": 12890
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** `RideResponse`. `400` if ride is not Open.
|
||||
|
||||
### `POST /api/rides/{id}/cancel`
|
||||
|
||||
Cancel a ride.
|
||||
|
||||
**Request body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"typeCode": "VehicleBreakdown",
|
||||
"reason": "Engine overheating"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Notes |
|
||||
|-------|------|-------|
|
||||
| `typeCode` | `CancelRequestType` | `VehicleBreakdown`, `DriverUnavailable`, `RouteIssue`, `Other` |
|
||||
| `reason` | `string` | Free text reason |
|
||||
|
||||
**Response:** `RideResponse`. `400` if ride is not Open.
|
||||
|
||||
---
|
||||
|
||||
## Loading Sheet
|
||||
|
||||
A Loading Sheet records the items loaded onto a vehicle before a ride. It belongs to a Ride.
|
||||
|
||||
### Status Flow
|
||||
|
||||
```
|
||||
Planned → Confirmed
|
||||
```
|
||||
|
||||
Items can only be edited while the sheet is `Planned`. Confirming the sheet commits stock movements (Warehouse → Vehicle).
|
||||
|
||||
### `GET /api/loading-sheets`
|
||||
|
||||
**Query params:**
|
||||
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `rideId` | `Guid?` | Filter by ride |
|
||||
| `status` | `LoadingSheetStatus?` | `Planned`, `Confirmed` |
|
||||
| `page` | `int` | Default `1` |
|
||||
| `pageSize` | `int` | Default `20` |
|
||||
|
||||
**Response:** `PagedResponse<LoadingSheetResponse>`
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"fK_RideId": "uuid",
|
||||
"status": "Planned",
|
||||
"populatedBy": "uuid",
|
||||
"populatedAt": "2026-09-17T07:00:00",
|
||||
"confirmedAt": null,
|
||||
"confirmedBy": null,
|
||||
"createdAt": "2026-09-17T07:00:00",
|
||||
"items": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"fK_ItemId": "uuid",
|
||||
"itemName": "Milo 400g",
|
||||
"fK_WarehouseId": "uuid",
|
||||
"warehouseName": "Colombo Warehouse",
|
||||
"quantityDispatched": 24.0,
|
||||
"fK_ShopPOItemId": "uuid | null"
|
||||
}
|
||||
],
|
||||
"orders": [
|
||||
{ "shopPOId": "uuid" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/loading-sheets/{id}`
|
||||
|
||||
**Response:** `LoadingSheetResponse` or `404`.
|
||||
|
||||
### `POST /api/loading-sheets`
|
||||
|
||||
Create a new loading sheet for a ride. The ride must exist. Only one `Planned` sheet per ride is allowed.
|
||||
|
||||
**Request body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"fK_RideId": "uuid"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** `201 Created` with `LoadingSheetResponse`. `400` if ride already has a planned sheet.
|
||||
|
||||
### `PUT /api/loading-sheets/{id}/items`
|
||||
|
||||
Replace all items on a `Planned` loading sheet. This is a full replace — any items not included are removed.
|
||||
|
||||
**Request body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"fK_ItemId": "uuid",
|
||||
"fK_WarehouseId": "uuid",
|
||||
"quantity": 24.0,
|
||||
"fK_ShopPOItemId": "uuid | null"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Required | Notes |
|
||||
|-------|------|----------|-------|
|
||||
| `fK_ItemId` | `Guid` | Yes | The item being loaded |
|
||||
| `fK_WarehouseId` | `Guid` | Yes | Source warehouse for this item |
|
||||
| `quantity` | `decimal` | Yes | Units to load |
|
||||
| `fK_ShopPOItemId` | `Guid?` | No | Links the load line to a specific ShopPO line item |
|
||||
|
||||
**Response:** `LoadingSheetResponse`. `400` if sheet is not `Planned`.
|
||||
|
||||
### `POST /api/loading-sheets/{id}/confirm`
|
||||
|
||||
Confirm the loading sheet. Commits stock: moves each item from its specified warehouse to the vehicle. Sheet becomes `Confirmed` and can no longer be edited.
|
||||
|
||||
**Request body:** _(none)_
|
||||
|
||||
**Response:** `LoadingSheetResponse`. `400` if already confirmed.
|
||||
|
||||
---
|
||||
|
||||
## Delivery Tasks
|
||||
|
||||
Delivery Tasks are the per-stop delivery jobs attached to a Ride. Each task maps to one ShopPO.
|
||||
|
||||
> **Note:** Auto-generation of delivery tasks from route/ShopPO assignments is deferred and not yet implemented. Tasks must be created manually or via a future dispatch endpoint.
|
||||
|
||||
### Status Flow
|
||||
|
||||
```
|
||||
Pending → Delivered
|
||||
↘ PartiallyDelivered
|
||||
↘ Failed
|
||||
```
|
||||
|
||||
### `GET /api/rides/{rideId}/tasks`
|
||||
|
||||
List all delivery tasks for a ride, ordered by `Sequence`.
|
||||
|
||||
**Response:** `DeliveryTaskResponse[]`
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "uuid",
|
||||
"fK_RideId": "uuid",
|
||||
"fK_ShopPOId": "uuid",
|
||||
"storeId": "uuid",
|
||||
"storeName": "Galle Mart",
|
||||
"sequence": 1,
|
||||
"status": "Pending",
|
||||
"customerConfirmed": false,
|
||||
"confirmedAt": null,
|
||||
"note": null,
|
||||
"items": [
|
||||
{
|
||||
"itemId": "uuid",
|
||||
"itemName": "Milo 400g",
|
||||
"quantity": 10,
|
||||
"quantityFulfilled": 0,
|
||||
"unitPrice": 850.00
|
||||
}
|
||||
],
|
||||
"createdAt": "2026-09-17T08:00:00"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### `GET /api/delivery-tasks/{id}`
|
||||
|
||||
**Response:** `DeliveryTaskResponse` or `404`.
|
||||
|
||||
### `PUT /api/delivery-tasks/{id}/quantities`
|
||||
|
||||
Set the fulfilled quantity for each item in the task. Call this before confirming.
|
||||
|
||||
**Request body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{ "itemId": "uuid", "quantityFulfilled": 8 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** `DeliveryTaskResponse`. `400` if task is already closed.
|
||||
|
||||
### `POST /api/delivery-tasks/{id}/confirm`
|
||||
|
||||
Mark the task as Delivered or PartiallyDelivered based on quantities set. Updates ShopPO fulfillment, creates stock movement (Vehicle → External), and records payment if applicable.
|
||||
|
||||
**Request body:** _(none)_
|
||||
|
||||
**Response:** `DeliveryTaskResponse`. `400` if task is not `Pending`.
|
||||
|
||||
### `POST /api/delivery-tasks/{id}/fail`
|
||||
|
||||
Mark the task as Failed with a reason note.
|
||||
|
||||
**Request body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"note": "Store was closed"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** `DeliveryTaskResponse`. `400` if task is not `Pending`.
|
||||
|
||||
---
|
||||
|
||||
## Returns
|
||||
|
||||
Returns are recorded against a ShopPO during the same store visit. Effects (credit deduction or item exchange) are applied immediately — no approval step.
|
||||
|
||||
> **Note:** The Create endpoint (`POST /api/shop-pos/{id}/returns`) is currently stubbed (`501 Not Implemented`) while the return flow is being redesigned. `GET` and `PATCH` endpoints are fully operational.
|
||||
|
||||
### `GET /api/returns`
|
||||
|
||||
List all returns.
|
||||
|
||||
**Response:** `SaleReturnResponse[]`
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "uuid",
|
||||
"shopPOId": "uuid",
|
||||
"mode": "CreditDeduction",
|
||||
"totalCreditAmount": 1700.00,
|
||||
"orderTotalAmountAfter": 13300.00,
|
||||
"isActive": true,
|
||||
"createdAt": "2026-09-17T11:00:00",
|
||||
"items": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"itemId": "uuid",
|
||||
"sku": "MILO400",
|
||||
"itemName": "Milo 400g",
|
||||
"quantity": 2.0,
|
||||
"reasonCode": "Damaged",
|
||||
"unitPrice": 850.00
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### `GET /api/returns/{id}`
|
||||
|
||||
**Response:** `SaleReturnResponse` or `404`.
|
||||
|
||||
### `GET /api/shop-pos/{id}/returns`
|
||||
|
||||
List all returns for a specific ShopPO.
|
||||
|
||||
**Response:** `SaleReturnResponse[]`
|
||||
|
||||
### `POST /api/shop-pos/{id}/returns`
|
||||
|
||||
> **501 Not Implemented** — return flow under redesign.
|
||||
|
||||
**Request body (for future reference):**
|
||||
|
||||
```json
|
||||
{
|
||||
"mode": "CreditDeduction",
|
||||
"items": [
|
||||
{ "itemId": "uuid", "quantity": 2.0, "reasonCode": "Damaged" }
|
||||
],
|
||||
"createdByUserId": "uuid"
|
||||
}
|
||||
```
|
||||
|
||||
| `mode` | Effect |
|
||||
|--------|--------|
|
||||
| `CreditDeduction` | `totalCreditAmount` is netted off the ShopPO's `TotalAmount` |
|
||||
| `ItemExchange` | Items are swapped; no monetary credit applied |
|
||||
|
||||
### `PATCH /api/returns/{id}/reasons`
|
||||
|
||||
Update the reason code on individual return line items. Only reason codes can be edited post-creation.
|
||||
|
||||
**Request body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{ "itemId": "uuid", "reasonCode": "Expired" }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** `SaleReturnResponse`. `409` if return is no longer active.
|
||||
|
||||
---
|
||||
|
||||
## Schemes
|
||||
|
||||
Schemes are promotional rules applied to ShopPOs (formerly Orders).
|
||||
|
||||
### Scheme Rule Types
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `SlabDiscount` | Tiered % discount based on order amount. Config: `SlabDiscountConfig` |
|
||||
| `FlatPercent` | Flat % discount on order total. Config: `FlatPercentConfig` |
|
||||
| `BuyXGetY` | Buy N of trigger item → get free payout item |
|
||||
| `FreeBundle` | Buy a bundle of items → get a free payout item |
|
||||
|
||||
### `GET /api/schemes`
|
||||
|
||||
List all schemes (paginated).
|
||||
|
||||
**Response:** `PagedResponse<SchemeResponse>`
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"name": "Q3 Volume Boost",
|
||||
"ruleType": "SlabDiscount",
|
||||
"ruleConfig": "{\"Tiers\":[{\"MinOrderAmount\":10000,\"DiscountPercent\":5}]}",
|
||||
"triggerItemId": null,
|
||||
"triggerItemName": null,
|
||||
"triggerQty": null,
|
||||
"freeItemId": null,
|
||||
"freeItemName": null,
|
||||
"freeQty": null,
|
||||
"bundleComponents": [],
|
||||
"regionId": "uuid | null",
|
||||
"regionName": "Western Province | null",
|
||||
"eligibleSegment": null,
|
||||
"dateRangeFrom": "2026-07-01T00:00:00",
|
||||
"dateRangeTo": "2026-09-30T00:00:00",
|
||||
"agentLimitExempt": false,
|
||||
"baselineExpectedSales": 500000.00,
|
||||
"isActive": true,
|
||||
"createdAt": "2026-06-15T09:00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/schemes/{id}`
|
||||
|
||||
**Response:** `SchemeResponse` or `404`.
|
||||
|
||||
### `POST /api/schemes`
|
||||
|
||||
Create a scheme. For discount-only schemes (`SlabDiscount`, `FlatPercent`), free-goods fields can be omitted. For `BuyXGetY` / `FreeBundle`, you can supply the free-goods config inline here or set it later via `PUT .../free-goods-rule`.
|
||||
|
||||
**Request body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Q3 Volume Boost",
|
||||
"ruleType": "SlabDiscount",
|
||||
"ruleConfig": "{\"Tiers\":[{\"MinOrderAmount\":10000,\"DiscountPercent\":5}]}",
|
||||
"regionId": null,
|
||||
"eligibleSegment": null,
|
||||
"dateRangeFrom": "2026-07-01T00:00:00",
|
||||
"dateRangeTo": "2026-09-30T00:00:00",
|
||||
"agentLimitExempt": false,
|
||||
"baselineExpectedSales": 500000.00
|
||||
}
|
||||
```
|
||||
|
||||
#### `ruleConfig` shapes
|
||||
|
||||
**SlabDiscount:**
|
||||
```json
|
||||
{ "Tiers": [ { "MinOrderAmount": 10000, "DiscountPercent": 5 }, { "MinOrderAmount": 20000, "DiscountPercent": 8 } ] }
|
||||
```
|
||||
|
||||
**FlatPercent:**
|
||||
```json
|
||||
{ "DiscountPercent": 3.5 }
|
||||
```
|
||||
|
||||
**BuyXGetY / FreeBundle:** `ruleConfig` is `null` — the payout is set via `PUT .../free-goods-rule`.
|
||||
|
||||
### `PUT /api/schemes/{id}`
|
||||
|
||||
Update scheme metadata. Cannot change `ruleType`.
|
||||
|
||||
### `DELETE /api/schemes/{id}`
|
||||
|
||||
Soft-delete (sets `IsActive = false`).
|
||||
|
||||
### `PUT /api/schemes/{id}/free-goods-rule`
|
||||
|
||||
Set the free-goods payout for `BuyXGetY` or `FreeBundle` schemes.
|
||||
|
||||
**Request body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"triggerItemId": "uuid | null",
|
||||
"triggerQty": 6,
|
||||
"freeItemId": "uuid",
|
||||
"freeQty": 1.0
|
||||
}
|
||||
```
|
||||
|
||||
- `BuyXGetY`: `triggerItemId` + `triggerQty` required (what store must buy).
|
||||
- `FreeBundle`: `triggerItemId` / `triggerQty` ignored — trigger is the bundle components.
|
||||
|
||||
### `PUT /api/schemes/{id}/bundle-components/{itemId}`
|
||||
|
||||
Add or update a component in a `FreeBundle` scheme.
|
||||
|
||||
**Request body:**
|
||||
|
||||
```json
|
||||
{ "qty": 4 }
|
||||
```
|
||||
|
||||
### `DELETE /api/schemes/{id}/bundle-components/{itemId}`
|
||||
|
||||
Remove a bundle component.
|
||||
|
||||
### `GET /api/shop-pos/{shopPOId}/scheme-eligibility`
|
||||
|
||||
Check which active schemes the ShopPO qualifies for.
|
||||
|
||||
**Response:** `SchemeEligibilityResponse[]`
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"schemeId": "uuid",
|
||||
"schemeName": "Q3 Volume Boost",
|
||||
"ruleType": "SlabDiscount",
|
||||
"eligible": true,
|
||||
"reason": null,
|
||||
"discountAmount": 750.00,
|
||||
"freeItemId": null,
|
||||
"freeQty": null
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### `POST /api/shop-pos/{shopPOId}/apply-scheme`
|
||||
|
||||
Apply a scheme to the ShopPO (create a redemption record and apply discount/free goods).
|
||||
|
||||
**Request body:**
|
||||
|
||||
```json
|
||||
{ "schemeId": "uuid" }
|
||||
```
|
||||
|
||||
**Response:** `SchemeRedemptionResponse`
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "uuid",
|
||||
"schemeId": "uuid",
|
||||
"shopPOId": "uuid",
|
||||
"discountAmount": 750.00,
|
||||
"freeItemId": null,
|
||||
"freeItemName": null,
|
||||
"freeQty": null,
|
||||
"freeItemCostValue": null,
|
||||
"redeemedAt": "2026-09-17T09:30:00"
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/schemes/{id}/spend`
|
||||
|
||||
**Response:** `SchemeSpendResponse`
|
||||
|
||||
```json
|
||||
{
|
||||
"schemeId": "uuid",
|
||||
"totalDiscountSpend": 45000.00,
|
||||
"totalFreeGoodsCostSpend": 12000.00,
|
||||
"totalSpend": 57000.00,
|
||||
"redemptionCount": 38
|
||||
}
|
||||
```
|
||||
|
||||
### `GET /api/schemes/{id}/roi`
|
||||
|
||||
**Response:** `SchemeROIResponse`
|
||||
|
||||
```json
|
||||
{
|
||||
"schemeId": "uuid",
|
||||
"baselineExpectedSales": 500000.00,
|
||||
"actualSales": 620000.00,
|
||||
"incrementalSales": 120000.00,
|
||||
"totalSpend": 57000.00,
|
||||
"roi": 1.105
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## System Config
|
||||
|
||||
Key-value store for system-wide configuration. All keys and values are strings. Only known keys have typed endpoints with validation.
|
||||
|
||||
### `GET /api/sys-config`
|
||||
|
||||
Return all config entries.
|
||||
|
||||
**Response:** `SysConfigResponse[]`
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"key": "InvoiceCreationMode",
|
||||
"value": "ModeB",
|
||||
"updatedAt": "2026-09-17T00:00:00",
|
||||
"updatedBy": null
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### `GET /api/sys-config/invoice-creation-mode`
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{ "mode": "ModeB" }
|
||||
```
|
||||
|
||||
### `PUT /api/sys-config/invoice-creation-mode`
|
||||
|
||||
**Request body:**
|
||||
|
||||
```json
|
||||
{ "mode": "ModeA" }
|
||||
```
|
||||
|
||||
| Value | Behaviour |
|
||||
|-------|-----------|
|
||||
| `ModeA` | Invoice is auto-created when a ShopPO is confirmed |
|
||||
| `ModeB` | Invoice must be created as a separate explicit step (default) |
|
||||
|
||||
**Response:** `SysConfigResponse`. `400` if value is not a valid `InvoiceCreationMode`.
|
||||
|
||||
### `GET /api/sys-config/{key}`
|
||||
|
||||
Generic getter by key string. `404` if key not set.
|
||||
|
||||
### `PUT /api/sys-config/{key}`
|
||||
|
||||
Generic raw string setter. Requires admin-level auth.
|
||||
|
||||
**Request body:**
|
||||
|
||||
```json
|
||||
{ "value": "SomeValue" }
|
||||
```
|
||||
|
||||
**Response:** `SysConfigResponse`. `400` if value fails validation for a known key.
|
||||
|
||||
---
|
||||
|
||||
## Key Relationships & Flows
|
||||
|
||||
### Core Entity Chain
|
||||
|
||||
```
|
||||
Store ──< ShopPO ──< ShopPOItem
|
||||
│
|
||||
└──► Invoice ──< Payment
|
||||
│
|
||||
└──► SaleReturn ──< SaleReturnItem
|
||||
│
|
||||
└──► SchemeRedemption
|
||||
|
||||
Ride (DeliveryRun) ──► LoadingSheet ──< LoadingSheetItem
|
||||
└──< DeliveryTask ──► ShopPO
|
||||
```
|
||||
|
||||
### ShopPO → Invoice Dependency
|
||||
|
||||
- `ShopPO.invoiceId` is `null` until an invoice is created.
|
||||
- In **ModeA**, the invoice is created automatically when the ShopPO is confirmed.
|
||||
- In **ModeB**, the invoice must be created via a dedicated endpoint (not yet exposed — planned).
|
||||
- An invoice can only exist for a confirmed ShopPO.
|
||||
|
||||
### Payment → Invoice Dependency
|
||||
|
||||
- Payments are always attached to an **Invoice**, never directly to a ShopPO.
|
||||
- `Invoice.paymentStatus` is computed:
|
||||
- `Pending` — no payments
|
||||
- `PartiallyPaid` — payments collected but total < invoice amount
|
||||
- `Paid` — fully settled
|
||||
|
||||
### Loading Sheet → Stock
|
||||
|
||||
- Confirming a loading sheet deducts each item from its **source warehouse** (`fK_WarehouseId` per line) and credits the **vehicle** stock location.
|
||||
- Each loading sheet line can optionally link to a `fK_ShopPOItemId` for traceability.
|
||||
|
||||
### Delivery Task → ShopPO Fulfillment
|
||||
|
||||
- Confirming a delivery task updates `ShopPOItem.quantityFulfilled` and transitions the ShopPO status:
|
||||
- All items fully fulfilled → `Fulfilled`
|
||||
- Some items partially fulfilled → `PartiallyFulfilled`
|
||||
|
||||
### Returns → ShopPO
|
||||
|
||||
- Returns always reference a `shopPOId`.
|
||||
- `CreditDeduction` mode: `SaleReturn.totalCreditAmount` is netted off `ShopPO.totalAmount` immediately.
|
||||
- `ItemExchange` mode: no monetary impact; items are swapped in stock.
|
||||
- Only the `reasonCode` on each return line can be edited after creation.
|
||||
|
||||
---
|
||||
|
||||
## What Is Not Yet Implemented
|
||||
|
||||
| Feature | Status | Notes |
|
||||
|---------|--------|-------|
|
||||
| ShopPO confirm endpoint | Pending | `PUT /api/shop-pos/{id}/confirm` — triggers InvoiceCreationMode check |
|
||||
| Invoice create (ModeB) | Pending | `POST /api/invoices` with `{ shopPOId }` |
|
||||
| Invoice print | Pending | `POST /api/invoices/{id}/print` |
|
||||
| Invoice finalize | Pending | `POST /api/invoices/{id}/finalize` |
|
||||
| Delivery task auto-generation | Deferred | Route-based dispatch not yet implemented |
|
||||
| Returns create | Redesign | `POST /api/shop-pos/{id}/returns` returns `501` |
|
||||
| Permission seeding | Pending | `loading.populate`, `loading.confirm` scopes |
|
||||
Reference in New Issue
Block a user