From 28bc4753467ceaec902cb8b8a21c196d3b92b444 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Fri, 6 Feb 2026 11:27:38 +0000 Subject: [PATCH] Autosave: 20260206-112737 --- config/__pycache__/__init__.cpython-311.pyc | Bin 159 -> 159 bytes config/__pycache__/settings.cpython-311.pyc | Bin 5552 -> 5640 bytes config/__pycache__/urls.cpython-311.pyc | Bin 1557 -> 1660 bytes config/__pycache__/wsgi.cpython-311.pyc | Bin 679 -> 679 bytes config/settings.py | 6 +- config/urls.py | 3 +- core/__pycache__/__init__.cpython-311.pyc | Bin 157 -> 157 bytes core/__pycache__/admin.cpython-311.pyc | Bin 212 -> 2924 bytes core/__pycache__/apps.cpython-311.pyc | Bin 524 -> 524 bytes .../context_processors.cpython-311.pyc | Bin 763 -> 763 bytes core/__pycache__/forms.cpython-311.pyc | Bin 0 -> 7518 bytes core/__pycache__/models.cpython-311.pyc | Bin 209 -> 9986 bytes core/__pycache__/urls.cpython-311.pyc | Bin 347 -> 1447 bytes core/__pycache__/views.cpython-311.pyc | Bin 1364 -> 20064 bytes core/admin.py | 38 +- core/forms.py | 107 +++++ core/migrations/0001_initial.py | 64 +++ ...r_supplier_alter_batch_options_and_more.py | 170 ++++++++ ...rnative_supplier_medicine_main_supplier.py | 24 ++ .../__pycache__/0001_initial.cpython-311.pyc | Bin 0 -> 3913 bytes ...ter_batch_options_and_more.cpython-311.pyc | Bin 0 -> 7046 bytes ...ier_medicine_main_supplier.cpython-311.pyc | Bin 0 -> 1422 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 168 -> 168 bytes core/models.py | 133 +++++- core/templates/base.html | 143 ++++++- core/templates/core/barang_keluar.html | 152 +++++++ core/templates/core/categories.html | 83 ++++ core/templates/core/edit_transaksi.html | 61 +++ core/templates/core/faktur_detail.html | 131 ++++++ core/templates/core/index.html | 397 ++++++++++------- core/templates/core/input_faktur.html | 86 ++++ core/templates/core/laporan_transaksi.html | 122 ++++++ core/templates/core/medicines.html | 238 +++++++++++ core/templates/core/suppliers.html | 86 ++++ core/templates/registration/login.html | 69 +++ core/urls.py | 16 +- core/views.py | 398 +++++++++++++++++- db/config.php | 6 +- requirements.txt | 1 + static/css/custom.css | 124 +++++- 40 files changed, 2473 insertions(+), 185 deletions(-) create mode 100644 core/__pycache__/forms.cpython-311.pyc create mode 100644 core/forms.py create mode 100644 core/migrations/0001_initial.py create mode 100644 core/migrations/0002_faktur_supplier_alter_batch_options_and_more.py create mode 100644 core/migrations/0003_medicine_alternative_supplier_medicine_main_supplier.py create mode 100644 core/migrations/__pycache__/0001_initial.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0002_faktur_supplier_alter_batch_options_and_more.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0003_medicine_alternative_supplier_medicine_main_supplier.cpython-311.pyc create mode 100644 core/templates/core/barang_keluar.html create mode 100644 core/templates/core/categories.html create mode 100644 core/templates/core/edit_transaksi.html create mode 100644 core/templates/core/faktur_detail.html create mode 100644 core/templates/core/input_faktur.html create mode 100644 core/templates/core/laporan_transaksi.html create mode 100644 core/templates/core/medicines.html create mode 100644 core/templates/core/suppliers.html create mode 100644 core/templates/registration/login.html diff --git a/config/__pycache__/__init__.cpython-311.pyc b/config/__pycache__/__init__.cpython-311.pyc index 423a6362b2322713e75da67a35e209e76169dbae..1ef46bfe8720a09423121488a0aac42d77735e78 100644 GIT binary patch delta 19 ZcmbQwIG>SwIWI340}wnu*fNoO3IH#U1#bWV delta 19 ZcmbQwIG>SwIWI340}xbw%b&-J!#~oR^o20SIpGZ_VtP$ScWsXQTQR7G9Sq{S?kk2h)pqPJX2#R1456}%of*^t)Nc`fk$<0qG%}KQ@$^>#5 rfw=h9=2L=sOl+bJybpM!Hrt7$Gm763mAJqlbVE$z0)sFFfsFtF4U|7V delta 130 zcmeCs*`Uq4oR^o20SKzTo+{SVJl)dbTg;&$ z&YR15b(okm^*8V0=VwwW;sI(b5(E+aK;jpNO>TZlX-=wLQ5ulT2*kzrHj4@6F|kQB V@IK%X+}tIS&d7a%K?sPz+5o7dAawu$ diff --git a/config/__pycache__/urls.cpython-311.pyc b/config/__pycache__/urls.cpython-311.pyc index 0b85e94ece283a83ff1af1d71f1b265c943eb37a..8275db06cbca59833e2b24cb2390669b18e6af09 100644 GIT binary patch delta 258 zcmbQr^M^-$IWI340}zxRY0XS$VPJR+;=lk0l=0bUqIy3M6GJL%3MY_cN#U9}OIDXT ziYJvlix;N2$Rrh{gfoR3D9Q&DP2s8HW?)#uJMoFJJnu3l28Pu@3;|U@tx-HFe8CJL ztjWLGjB!7cY87WDcDS!>LsC!|kEzaVFEQOxp+nB_$lt1B#4H&{3tTsnk0!aKqzL|^1pyuzz^kwxhW Ui_!;n7JjA%?hibZx3VSx0C#RbH2?qr delta 132 zcmeyvGnGeuIWI340}xbw%g@wdVPJR+;=lkql<}EsqIy45I@81l;-Z|X>{)y;1u0xr z+zbqBxFicD5ulxGC7L^*ka8B%~)lXr6*<9?>iPAqDSOdlC0XR+2yViVzDW#(sU M;QqihS%xhE09P6ry#N3J diff --git a/config/__pycache__/wsgi.cpython-311.pyc b/config/__pycache__/wsgi.cpython-311.pyc index 9c49e09df194d2dbcad4868349c9177db4b15571..f9b82d372dc5d066098b51ada310dff3345a4ef1 100644 GIT binary patch delta 20 acmZ3^x}24JIWI340}wnu*s_s(4if-3PzA97 delta 20 acmZ3^x}24JIWI340}xbw%iqX7hY0{RMg?d9 diff --git a/config/settings.py b/config/settings.py index 291d043..e9dc34b 100644 --- a/config/settings.py +++ b/config/settings.py @@ -133,9 +133,9 @@ AUTH_PASSWORD_VALIDATORS = [ # Internationalization # https://docs.djangoproject.com/en/5.2/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = 'id' -TIME_ZONE = 'UTC' +TIME_ZONE = 'Asia/Jakarta' USE_I18N = True @@ -180,3 +180,5 @@ if EMAIL_USE_SSL: # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +LOGIN_REDIRECT_URL = '/' +LOGOUT_REDIRECT_URL = '/accounts/login/' diff --git a/config/urls.py b/config/urls.py index bcfc074..fecf250 100644 --- a/config/urls.py +++ b/config/urls.py @@ -21,9 +21,10 @@ from django.conf.urls.static import static urlpatterns = [ path("admin/", admin.site.urls), + path("accounts/", include("django.contrib.auth.urls")), path("", include("core.urls")), ] if settings.DEBUG: urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets") - urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) \ No newline at end of file diff --git a/core/__pycache__/__init__.cpython-311.pyc b/core/__pycache__/__init__.cpython-311.pyc index 74b111269bd81aac528770a53e2f849291524d56..ff362886ec2a3a3f9900d114f00d5433be174c53 100644 GIT binary patch delta 19 ZcmbQsIG2%oIWI340}wnu*fNoO5&$nn1!({P delta 19 ZcmbQsIG2%oIWI340}xbw%b&>mG1t0(b diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index a5ed392d6714413db63120e4233d2e96cbadb5de..7f66d9bfa1ffa4036ef2eea552af1754c2ede772 100644 GIT binary patch literal 2924 zcmbVOOK;mo5Z)z4nvpC=wq+-A6IXE)yF%mW&^WoY0oo=F(8TJ)1zPYz(Au?4n-6tK zp>a+skluYue}Ri2f1*Dj26Pbi6riWvWW=YOGDC`vO%bhIQW|n+M<3^#m-uJ7TqJOO z^VjdrZyF(g<03uu;mw;3g^*{25k_6&QIk@_6sEe0r<$r)Fbke$YMyTD-k3S&8KwdI z1x~lmr9f8v3|ukol)jW&F#VK%Jk=`%^yPHL$Mz{9FW{RT3VA@-*i*s`u$oA<`zqML zH#zp<@*-Uj^Lz%;$U!^@h{ZD_Rx*fc4q^!q$Ipy7nL#Y%AeI4f;>?Is8AL4y@jM__ z&Wt#nLDX{)CjoKl%!pMsy+-QQ*O;q1m72{w$A68vuItkH%8vMEAa>^ywO?j_)(|9AM;ix z3Pj`AKS0FlvTDM?Ba7YNiwoFmKccgYAyPZ(;pOflJQGG}mG9TTW}}?;Q<4uo zE<|ax(tcCJosKMYd?%8nD1d&kLhLuGhmjq1LP!Z#u2=*C_t)`$2}HA=o0lXNHMGj) z;~MHvV0K3G@hukMN8Q!_)MED`o(XTX|DU{z@$^VYL)UrfR2g5#sghP>i9c>TV%LgG z(SR28BPZIGMJKf49nPQ*Z-N)VeHkutA_Yp?xG6paZI=vlR3J91_xrJLpgG@d@ zMI8#v(a1nqMUJ;lrV>q?^%*(AwqXbdH*ro1XI-eEmpTkW?uG*1P>8_S5;2CtK!F2G z;0uNa|1MI6`r#5@qh#-XKaPM$r%Gg&w^y zP*l1!AfsaMdT(ms5I2rcAj-vK`(lc6>&SrZc24q%h_%0#ks&dbZi00fT@wru2PWC8#&^aS1j delta 20 acmeBS>0#ks&dbZi00dRv@;7obG64WD>I9Vl diff --git a/core/__pycache__/context_processors.cpython-311.pyc b/core/__pycache__/context_processors.cpython-311.pyc index 75bf2234fb21a6b62efc5cec11af9512dd0c9616..86948bc971e96a5726f863021a128a1583b0ab3c 100644 GIT binary patch delta 20 acmey(`kR$|IWI340}wnu*s_uP0}}v5a0aaa delta 20 acmey(`kR$|IWI340}xbw%iqZTfe8RYW(H&c diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..74fd7301a8181a94fd209b5501fa91e1d319145b GIT binary patch literal 7518 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 literal 209 zcmZ3^%ge<81k-=yXIcX3#~=<2FhLogg@BCd3@HpLj5!Rsj8Tk?3@J>(44TX@K?*b( zZ?Wa(r=;c-`)M-W;!Md(%uCPLOGzqX21>4E_zY6>OHV%|KQ~psG^sSNq*On(A~m_R zB)>?%JijQrxF9h(RX;huC{-U~j9x+IFAf_ZyEG@&u80Guoe_wOWr4&8W=2NF8w@fR Ku%RM0pb7xLi8T)Z diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 5a69659f6c6e0ae848e54157af197c543a09315f..b11b47134e34139d03e2cb312d85bf740a4c48b4 100644 GIT binary patch literal 1447 zcmaKrzfaph6vxlx0trbV{3;|67^<{|05AhII(rMOUY7} zN(>#?nvpL3p?1i>(ao-IKwdksA+a&_U7WZDS-m*t_rCArclO&#BFlvZ-D(DdXPho%J&8?v=~ z28Ya@=o^*nn;!D;u3jZPT9d1!-diP#T2^bs6unf(mNeD0>f;^P6vZVm;jsE~qu@kx^NM%)TNi#FAC5-nS)FxwbP`A(|~y2>ZU|`}L%3sN$Avm3R5d zi5+4|ZLc2FWcV(*CVB&HOR~T<<%+4+vw_LqD< zJ>VsjJ$PY#-lm#Kc{5So8ncvU_Na%7_$GATa+!!_okAGiWZpQqj4f1k z#n#9o&A`LFbj+79pGO2iI7JiA)$0@uIajaqZWN6lt{e>>6+e_3+YZjVIL~nY6vsfF zIJVj7OmoV?%PwAKc)2Az@mpV`>`uXXCOLT1#hVOo{*OP(HKJ_nZc}t{!NmoJ3qC#7 z$g}KhbMdO|#K#*&mb=${=ioILuQ9yVUNzHrz{cmBu;{9bR~cUQ(@Zu-SZ1b~bMTUj zml$61Me#MkU1c^yu{p8KTXzK+$mX!dFlCjrA0Z# zMIgg&v6f|~mKTE+-r@!d15GJOEy^oi$?zE{&G1V{KO=uKKZ^-BACLz!z1WLkvLB12 u#RCTM3#jM=8v`q6gG+}{N5~Ae3oP;%S>&&<$bVpF;%92$2Eig8pnd>i%RO`e diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 2a36fd69370b38a98d8b01bf8eb9817c42f16ed6..1004ca810a3c878496b6dec9261e830318f47aba 100644 GIT binary patch literal 20064 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 literal 1364 zcmZ`(&1)M+6ra_{dS%J>I&SM!uH%hUh$T{~CE!96oTjQ1oHkCdQ$lozPf-Q6#oM~=1-7}AYwoe=&3h>36z}rW~~)fO5P4{-h1=r&HMPV zzfVm~BA|nRhVUX&zZ=1NEJDd#(kRMro?7~GlVKiARAFbK7y={g8`rq_)Wa;XDEltUWCmB zkq~sth&3ZeP{;A878u54V{Oaty2i>_vx<&kIwj51DaMXgGg(=)ND+pj!HI^Q9g`Br z#tzdA%!;PvWg3a1>()Z2BR!#4DWHiJ1dw>FOuS)~xge&2p-9tZ06jh%7)=`o)~k%rY>m)oo?Fy$ z*3WOp#5FJD)_FvD(?z&0py>SutcBjF^RHFyMAbU#a#vk`t*)G?D;+i6Rnx7Fo|by~ z_(Z$b)~@ZR_tUQyT0itB&pp5LYvy^Tl^e+D)6aYJ%l+g^CzEdC%omzVp>MZ5DOS%5y(&3}_z$uCKWqq#L7+bb`Jo5BhyL_xVL|A z-+ueOw-1ZIx3q)=c>eUyzo@0KApDCp>W{Bhc{2l*uLL9@Q4>}~C?!qGdsaMo?}}Fx zB;kR8JbxCD7kf79bA&yQAbbt4`>gnoxFDo`hphQDEzwY3&6$d=8oEXOc>`h1%GPvw zakbWX(+3v65-?wPMMC0=Cnv0UB?0?3{PhV)LLTU8+6XjyL?j|F@|FF2Ucdpb`>aW* ziE9SyJt$BP?unoSZ$l@fe zbQ0%ul3b^yZU?t+T&dSLni{o8qZX~#Pzv>yZ=A)R3zYj#?;Y%)jEkE;f&G4t?d@^# zGp< z_#HSO`3anla%>IGAx`f?oQ{>>KgY)zX3sJIOK$0{C-zcN{HxP!K^75zG)mhVz8o)q za4z4xJO9JwduMsbHJy$4oOh)iO+ap)q`~=mPF<}p&U zs*Vr;&Bi^}X7O-@tty!;A5ec$SDzID50rM=4?iLPMP*gPwi)MI)c;s1SVhh3ahtx3 zVH=v6sI?YsfNCb)mgnQFWu|KNjIm|*LY><=WWG#$Xj69GP_r2DA-tv(HQNOd%&WR= z*+zErkcC=WG;cydLp4fe#Reu^mUZI`SwV<~iUq{zcEyYJl;I$RuEChxI-cuL_-04WoskTL1EUamb zX*5uWPMi3`xW0u(e`JBNwP-53rDR#C(1?B9knK_dr#-N!H1~8noKavXmz-Cu;wJQY ze!aA~-BRm(jJ2X-vfingN3{84te3D=R8&PjWVZ|;qYnz#O$LtmC@NS5JlQq(Zi!?FwvYK(noHK`DWaA0T{;R4nX z*0T`ZCWAtor}+t+XSW)YT|HESX>)unE{_I zs(I`VhGro!)SUhpj;bGR080jy&?*h^;9#^iO;0UMPtDw?K{m5ya>n(f+X^h)UzDFN zJ$XVyMgi9@U;hx^06^h~j|SI`JRU5r7InKgh_`SS;F^P980Mx`fG`+jEDj!D+6IS) zhKA)D2?K?a$u47Du-|bj2HSqo_rPFdcEfTR0qMam`-sx~+>6Z&6HeQL1JF#&R`QRYhLy|+g zekVEf(k97?N^-(UPLRlCwaMGoS`7=){@p)TJyLsX^-3rhdM$uBVxrpde_FqTbluwV zJ6*S4X7+8;HB;%Dak^$mWwJr6xlbkBKSb9uxi^h6(d4x;ao zp5dJ}r)PM-m-I|ldM2HoNfP<+AQ~r$(Vgc`V)RuTNz7Ifvrb}`L>`@AQtn`VCoFBE8F% z-esqEnM6K4LCIOJ{u?DG?0idnz$tmiDS5~#dB`aNs(yr$8<(Ku2Dv%5yX@Q?gS9^5 z?4Oj&(x+^#{@=3IL4rd-GKYL7onWXEA93O?YsUw~OJZY{*q9R=Ba!#tvimO5eP^fN z>Av%_Z-4RIXiTsoMsj{AdZ-*&Wj^g{VyJ|kNNw0u1Nc7%*|#4Xm!B-8#qu;Y32f`(O_eoCs&?N z-DLASW`&#j9BMxVCfrp0q4@`BvVU{dDu}8_6ve7v5K}}*y59rg7P;tK^^c4Gs&HO> R8FC_hui3!S1*06pe*k0+@tFVs literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/0002_faktur_supplier_alter_batch_options_and_more.cpython-311.pyc b/core/migrations/__pycache__/0002_faktur_supplier_alter_batch_options_and_more.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf003050db7bf8d961e5fb8b13d1f903c20da089 GIT binary patch literal 7046 zcmbtZO;Fob9+%}0Y=emrBoxAjfrQpH!GW~hwwtDf00|fj#SjwOL=k?%2xQ5X`X7a|Mz4WFu{aYlHZ5) zf4~3tUr+G2P$u6Ow_warimzP1v64w6AVdJbjz1fktpk_ z3N7shE0dr6Am?|CNcpd_$dz`jx>*J|IZ`r+MdXfev%vvC9yq@n%zX6mmu=RA_$20x$U42Vk73;T1+LX%=y) z6}1)49+B)ErEYfCgIw)}w~k~SiWK^eNY`IOG2*U_iwv%=Wn3^|eCIOW$~MY(|{YIE{LjLFy7Zt9e_yNqs9l!H-wt2Sy+kmNF| z&9?xWK4m1t(B)t3ff*D>!>9B>49(VfpcT!X(gU$0diSElsr8Rhf&fDO8(V_me3$tuC3lDV2z-m!=3A8Wr3(_;#&HYf_(bdLfx@)x) zZNmO>%L>dn`$pJAdQz$;T1{83Tp8gdMKUBowtQ2V{3M@R*lHC2ee2zMLX`l@m3xHM2YjI9Ux#f|5=PGDzBh#C0Jj8}{SI3bwJG z72%n6~cQ>7L+afX|Q4;UNEAriqRDH z5icMVWo?qygEr!`a!v#F+?I!$%(pIyG6HxtYj5oRjt*Ksk@SB=(ojOk2odK2;OIi6iFKDmeoYLRiY+;#ts z|5rIdF(hMKBjV;8XL1=?*s$#o(nVRW>f>lJlYQoBW;m3G`!rMzIYwBgS%TImO7u#K&R^wAEsd zRBcPoLY0USZ^ZWh-V%fNne6#{~MyZnn+$&1^Zy1bY7*H2poLmX3e7oW<=!-##?khju(KFW^-kD;jRsi|xAE zuH&##YKh?f(eDS%{?VNj5Zv3s{c^EiHv459&Xii(aYyv&IkO}BV(=fA%+Z_XXdJJm za0e=OAhQGEuvlv8!I3N9`^?A{{PBJK;0gZdcV&jT$tGCnzF_Z>PG+}B*OzC1r0px( z!kv~UY^mif+&T1g(Ci#~(Y8B{I}^pugxQ(E;rUWaH}1Xq^r6{%b+--oP8EBn%-$&+ zzF7`6v^AGGrY~B0J686(&o-AC(wuD$1b<|pc}1FX&%Plo%e9~eyODs zcU^iqWp-V9@!swR?wT)l&6{2GIJ{79^nn&(rtjU-+am|GXbll9pm{}_au*Z0NE~_r z7?mzweVM>*U%&+m2pKf5NK^LjlbtVJjJ*uvwj^Bj$wps(g4^!GRflY#bn$(hxR2X- zxL!loLxk%twL`%5mbm_J2TiX3c?y5_Ip!9N+@i@XmK!`oRA^q2hJ@(hMHk7Ui!VOk z9mPuzFeel_!Q_Pc%X%DM_{s$4CX3vp$xYT@a=ye35KaTn*9($4eBB(rX%5fh`)Mpc zfnbcYNfM>4OC(C6g(!vgL`Yls=fX|~bF)Qm*5qdEuZfhnQ!CsDdFCkmylp3rxm!i< zmdV|!!wS~4SJ$L=&tPt$$Ss)MLj5(zSxLgm@4-BC>n1UB*IfrcO0w1oy9+Otc5nadt~t4EPTn^s(}48IHA&!S*xRHN zS($XL8EV`NHEyP!aRI)�`?D9(*ye`v`OMMQ+~Y=Ie`ejE|R6Sj%JXNs)VEa!=|* z?E9x}XBKnQMQ+;Urt7Z(ww1{L5Oddx+%=QCR)58S<5B!>&c47rH`obcr6il8#zNFs zh#CvM&RAWfLY=1-s^eH+;D=uW<|MmFFa=hk(3=!`lR|ISg9bkw4-J4-H`C-|j{3pR|_ zhV6d`4LjQ-Ee&MW*~)XeLmRGc&;r$EbpJ=UeD+@DF#)F>@-X_@f6d-RUf2ErQ}XW; zolunBE|&}TFRnh!MC_}?bYbTzG4J5lU1i@I>++SE*SFsW&G3aE>BCpYkWk6~5Bikc AD*ylh literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/0003_medicine_alternative_supplier_medicine_main_supplier.cpython-311.pyc b/core/migrations/__pycache__/0003_medicine_alternative_supplier_medicine_main_supplier.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9e2b897c783ee36c865e516c4a1d688ee5547ba7 GIT binary patch literal 1422 zcmbtU%TLrm7@tnNr7c?`4`Ym)fDmynyQo)VLgFq7;(AyxaSt@@&aiapi#|X|Bpfu+ z8#n(0)WE?%!Gi~n95#l`$;6Yl5;$>krn~LRBYJS!e&2jEUw^;(9_kx5GuFgY%g&q@vh^>gN zz7zln^ej?KPVJVM&5~%D)#^{zP!5TxZ(rJ_UVEA6zI--<3!KV-arRTr{J%Jxu%1UM z&D#KxpQibu$rBo`kIB8Msj2(glD-l~z7|BDXPMa7bStFN(8I>E=6XqjHQhm)&0djg zjLe4VU^0^UIkW<6ID2`XT>W!7bq+RFMp#TaHiv6nw$W+E#!!|DBFPz+?yL~Oi7boA zuA|Ya;t-R(k1ajKNON=>6BxQAzlMFo4REqj;AuCo4(RL)iC1pP#18BikRm(j^>dYI{>4d`w-kcxa^L_K~9XpMX{r_uS^`V Ljgw1ulQaAViuHm+ literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/__init__.cpython-311.pyc b/core/migrations/__pycache__/__init__.cpython-311.pyc index 9c833c8090679ee4c9ffd863c8f7abb0bb73a5db..d364bdb5736512feeba49a012aced456d4c1aada 100644 GIT binary patch delta 19 ZcmZ3%xPp;;IWI340}wnu*fNoOE&wpx1&ROw delta 19 ZcmZ3%xPp;;IWI340}xbw%b& - - + - - {% block title %}Knowledge Base{% endblock %} - {% if project_description %} - - - - {% endif %} - {% if project_image_url %} - - - {% endif %} - {% load static %} - - {% block head %}{% endblock %} + + + {{ project_name|default:"DN-WRS" }} + + + + + + + + + + + + + + {% block extra_head %}{% endblock %} - - {% block content %}{% endblock %} - + {% if user.is_authenticated %} + + {% endif %} - +
+ {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} +
+ +
+ {% block content %}{% endblock %} +
+ +
+

© {% now "Y" %} {{ project_name|default:"DN-WRS" }}

+

Sistem Manajemen Gudang Premium

+
+ + + + {% block extra_js %}{% endblock %} + + \ No newline at end of file diff --git a/core/templates/core/barang_keluar.html b/core/templates/core/barang_keluar.html new file mode 100644 index 0000000..5da9a0f --- /dev/null +++ b/core/templates/core/barang_keluar.html @@ -0,0 +1,152 @@ +{% extends 'base.html' %} +{% block content %} +
+
+
+
+

+
+ +
+ Barang Keluar +

+
+ {% csrf_token %} +
+ + {{ form.medicine }} +
+
+ + {{ form.batch }} +
+
+
+ + {{ form.quantity }} +
+
+ + {{ form.note }} +
+ +
+
+
+
+
+
+
+
+

+
+ +
+ Transaksi Terakhir +

+ 10 Transaksi +
+
+ + + + + + + + + + + + {% for t in transactions %} + + + + + + + + {% empty %} + + + + {% endfor %} + +
BarangBatchJumlahWaktuAksi
+
{{ t.medicine.name }}
+
{{ t.note|default:"-" }}
+
{{ t.batch.batch_number }}-{{ t.quantity }}{{ t.created_at|date:"d/m/y H:i" }} + + + +
+ + Belum ada transaksi keluar. +
+
+
+
+
+
+{% endblock %} + +{% block extra_head %} + + +{% endblock %} + +{% block extra_js %} + + + +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/categories.html b/core/templates/core/categories.html new file mode 100644 index 0000000..706660c --- /dev/null +++ b/core/templates/core/categories.html @@ -0,0 +1,83 @@ +{% extends 'base.html' %} +{% block content %} +
+
+
+
+

+
+ +
+ Tambah Kategori +

+
+ {% csrf_token %} + {% for field in form %} +
+ + {{ field }} + {% if field.help_text %} +
{{ field.help_text }}
+ {% endif %} + {% for error in field.errors %} +
{{ error }}
+ {% endfor %} +
+ {% endfor %} + +
+
+
+
+
+
+
+
+

+
+ +
+ Daftar Kategori +

+ {{ categories|length }} Total +
+
+ + + + + + + + + + {% for c in categories %} + + + + + + {% empty %} + + + + {% endfor %} + +
Nama KategoriDeskripsiAksi
+
{{ c.name }}
+
{{ c.description|default:"-"|truncatechars:100 }} + + + +
+ + Belum ada data kategori. +
+
+
+
+
+
+{% endblock %} diff --git a/core/templates/core/edit_transaksi.html b/core/templates/core/edit_transaksi.html new file mode 100644 index 0000000..ce63011 --- /dev/null +++ b/core/templates/core/edit_transaksi.html @@ -0,0 +1,61 @@ +{% extends 'base.html' %} +{% block content %} +
+
+
+
+ + +

+
+ +
+ Edit Transaksi +

+ +
+
+
+ BARANG + {{ transaction.medicine.name }} +
+
+ TIPE + + {{ transaction.get_transaction_type_display }} + +
+
+ BATCH NUMBER + {{ transaction.batch.batch_number }} +
+
+
+ +
+ {% csrf_token %} +
+ + +
Perubahan jumlah akan otomatis memperbarui stok pada batch.
+
+
+ + +
+ +
+ Batal + +
+
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/faktur_detail.html b/core/templates/core/faktur_detail.html new file mode 100644 index 0000000..4e1a61d --- /dev/null +++ b/core/templates/core/faktur_detail.html @@ -0,0 +1,131 @@ +{% extends 'base.html' %} +{% block content %} +
+ +
+
+
+
NOMOR FAKTUR
+

#{{ faktur.faktur_number }}

+
+
+
SUPPLIER
+
+ {{ faktur.supplier.name|default:"Internal" }} +
+
+
+
+
+ +
+
+
+
+
+
+ +
+ Input Barang +
+
+ {% csrf_token %} + {% for field in form %} +
+ + {{ field }} + {% if field.errors %} +
{{ field.errors }}
+ {% endif %} +
+ {% endfor %} + +
+
+
+
+
+
+
+
+
+
+ +
+ Rincian Barang +
+ {{ items|length }} Item +
+
+ + + + + + + + + + + + {% for item in items %} + + + + + + + + {% empty %} + + + + {% endfor %} + +
BarangNo. BatchKadaluarsaJumlahHarga Beli
+
{{ item.medicine.name }}
+
{{ item.medicine.sku }}
+
{{ item.batch_number }}{{ item.expiry_date|date:"d M Y" }}{{ item.quantity }} {{ item.medicine.unit }}Rp {{ item.buying_price|floatformat:2 }}
+ +

Belum ada barang diinput untuk faktur ini.

+
+
+
+
+
+
+{% endblock %} +{% block extra_head %} + + + +{% endblock %} +{% block extra_js %} + + + +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/index.html b/core/templates/core/index.html index faec813..6109bca 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -1,145 +1,260 @@ {% extends "base.html" %} - -{% block title %}{{ project_name }}{% endblock %} - -{% block head %} - - - - -{% endblock %} +{% load static %} {% block content %} -
-
-

Analyzing your requirements and generating your app…

-
- Loading… +
+ +
+
+
+
+ +
+
TOTAL BARANG
+

{{ total_medicines }}

+
+
-

AppWizzy AI is collecting your requirements and applying the first changes.

-

This page will refresh automatically as the plan is implemented.

-

- Runtime: Django {{ django_version }} · Python {{ python_version }} - — UTC {{ current_time|date:"Y-m-d H:i:s" }} -

-
-
-
- Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC) -
-{% endblock %} \ No newline at end of file +
+
+
+
+ +
+
TOTAL STOK
+

{{ total_stock }}

+
+
+
+
+
+
+
+ +
+
STOK MENIPIS
+

{{ low_stock_count }}

+
+
+
+
+
+
+
+ +
+
KADALUARSA
+

{{ expired_count }}

+
+
+
+ + +
+ +
+
+
+
+
Pergerakan Stok
+ 7 Hari Terakhir +
+
+ +
+
+
+
+ + +
+
+
+
+
Pantauan Kadaluarsa
+

Segera kadaluarsa (90 hari ke depan)

+
+
+
{{ near_expiry_count }}
+

Item memerlukan perhatian segera

+ +
+
+
+
+
+ +
+
+
+
+
+
+ Data Barang Terbaru +
+ + Kelola Semua + +
+
+ + + + + + + + + + + + + {% for medicine in recent_medicines %} + + + + + + + + + {% empty %} + + + + {% endfor %} + +
SKUNama BarangKategoriStokStatusAksi
{{ medicine.sku }}{{ medicine.name }}{{ medicine.category.name }} + {{ medicine.total_stock }} + {{ medicine.unit }} + + {% if medicine.status == 'Tersedia' %} + Tersedia + {% elif medicine.status == 'Stok Menipis' %} + Stok Menipis + {% else %} + Habis + {% endif %} + + + + +
+ + Belum ada data tersedia. +
+
+
+
+
+
+{% endblock %} + +{% block extra_js %} + +{% endblock %} diff --git a/core/templates/core/input_faktur.html b/core/templates/core/input_faktur.html new file mode 100644 index 0000000..b8e874b --- /dev/null +++ b/core/templates/core/input_faktur.html @@ -0,0 +1,86 @@ +{% extends 'base.html' %} +{% block content %} +
+
+
+
+

+
+ +
+ Input Faktur Baru +

+
+ {% csrf_token %} + {% for field in faktur_form %} +
+ + {{ field }} + {% if field.errors %} +
{{ field.errors }}
+ {% endif %} +
+ {% endfor %} + +
+
+
+
+
+
+
+
+

+
+ +
+ Riwayat Faktur +

+ {{ fakturs|length }} Total +
+
+ + + + + + + + + + + + {% for f in fakturs %} + + + + + + + + {% empty %} + + + + {% endfor %} + +
No. FakturSupplierTanggalTipeAksi
{{ f.faktur_number }}{{ f.supplier.name|default:"-" }}{{ f.date }} + + {{ f.get_faktur_type_display }} + + + + Input Barang + +
+ + Belum ada data faktur. +
+
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/core/laporan_transaksi.html b/core/templates/core/laporan_transaksi.html new file mode 100644 index 0000000..b38b49b --- /dev/null +++ b/core/templates/core/laporan_transaksi.html @@ -0,0 +1,122 @@ +{% extends 'base.html' %} +{% block content %} +
+
+
+

+
+ +
+ Laporan Keluar Masuk Barang +

+
+ +
+
+ +
+ + + + + + + + + + + + + {% for t in transactions %} + + + + + + + + + {% empty %} + + + + {% endfor %} + +
WaktuBarangTipeJumlahKeteranganAksi
+
{{ t.created_at|date:"d M Y" }}
+
{{ t.created_at|date:"H:i" }} WIB
+
+
{{ t.medicine.name }}
+
Batch: {{ t.batch.batch_number|default:"-" }}
+
+ {% if t.transaction_type == 'IN' %} + Masuk + {% elif t.transaction_type == 'OUT' %} + Keluar + {% else %} + Penyesuaian + {% endif %} + + {% if t.transaction_type == 'OUT' %} + -{{ t.quantity }} + {% else %} + +{{ t.quantity }} + {% endif %} + {{ t.note|default:"-" }} +
+ + + + +
+
+ + Belum ada transaksi stok. +
+
+
+
+ + + + +{% endblock %} + +{% block extra_js %} + +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/medicines.html b/core/templates/core/medicines.html new file mode 100644 index 0000000..612fd9c --- /dev/null +++ b/core/templates/core/medicines.html @@ -0,0 +1,238 @@ +{% extends 'base.html' %} +{% block content %} +
+
+
+
+
+ +
+
Total Barang
+

{{ total_count }}

+
+
+
+
+
+
+
+ +
+
Stok Menipis
+

{{ low_stock_count }}

+
+
+
+
+
+
+
+ +
+
Status Aman
+

{{ total_count|add:"-"|add:low_stock_count|default:0|cut:"-" }}

+
+
+
+
+ +
+
+
+

+
+ +
+ Manajemen Data Barang +

+
+ + + Export Stok Menipis (PDF) + +
+
+ +
+ + + + +
+ + + + + + + + + + + + + + + {% for m in medicines %} + + + + + + + + + + + {% empty %} + + + + {% endfor %} + +
SKUNama BarangKategoriSupplier UtamaSupplier AlternatifStokStatusAksi
{{ m.sku }} +
{{ m.name }}
+
+ {{ m.unit }} +
+
+ {{ m.category.name }} + +
{{ m.main_supplier.name|default:"-" }}
+ {% if m.main_supplier %} +
{{ m.main_supplier.phone|default:"N/A" }}
+ {% endif %} +
+
{{ m.alternative_supplier.name|default:"-" }}
+
+
+ {{ m.total_stock }} +
+
Min: {{ m.min_stock }}
+
+ {% if m.status == 'Tersedia' %} + + Tersedia + + {% elif m.status == 'Stok Menipis' %} + + Menipis + + {% else %} + + Habis + + {% endif %} + + +
+
+ +
Tidak ada data ditemukan
+

Coba ubah kata kunci pencarian atau filter Anda.

+ Reset Semua Filter +
+
+
+
+
+ + + + + +{% endblock %} \ No newline at end of file diff --git a/core/templates/core/suppliers.html b/core/templates/core/suppliers.html new file mode 100644 index 0000000..027c603 --- /dev/null +++ b/core/templates/core/suppliers.html @@ -0,0 +1,86 @@ +{% extends 'base.html' %} +{% block content %} +
+
+
+
+

+
+ +
+ Tambah Supplier +

+
+ {% csrf_token %} + {% for field in form %} +
+ + {{ field }} + {% if field.help_text %} +
{{ field.help_text }}
+ {% endif %} + {% for error in field.errors %} +
{{ error }}
+ {% endfor %} +
+ {% endfor %} + +
+
+
+
+
+
+
+
+

+
+ +
+ Daftar Supplier +

+ {{ suppliers|length }} Total +
+
+ + + + + + + + + + + {% for s in suppliers %} + + + + + + + {% empty %} + + + + {% endfor %} + +
Nama SupplierKontakTeleponAksi
+
{{ s.name }}
+
{{ s.address|truncatechars:40 }}
+
{{ s.contact_person|default:"-" }}{{ s.phone|default:"-" }} + + + +
+ + Belum ada data supplier. +
+
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/core/templates/registration/login.html b/core/templates/registration/login.html new file mode 100644 index 0000000..c742327 --- /dev/null +++ b/core/templates/registration/login.html @@ -0,0 +1,69 @@ +{% extends "base.html" %} +{% load static %} + +{% block content %} +
+
+
+
+
+
+ +
+

{{ project_name|default:"DN-WRS" }}

+

Masukkan kredensial Anda untuk mengakses portal

+
+ + {% if form.errors %} +
+ + Username atau password salah. +
+ {% endif %} + +
+ {% csrf_token %} +
+ +
+ + + + +
+
+
+ +
+ + + + +
+
+ +
+ +
+ + +
+
+
+
+ Diproteksi oleh DN-Security +
+
+
+ + +{% endblock %} \ No newline at end of file diff --git a/core/urls.py b/core/urls.py index 6299e3d..898bcdc 100644 --- a/core/urls.py +++ b/core/urls.py @@ -1,7 +1,17 @@ from django.urls import path - -from .views import home +from . import views urlpatterns = [ - path("", home, name="home"), + path('', views.home, name='home'), + path('medicines/', views.medicine_list, name='medicine_list'), + path('medicines/export-low-stock/', views.export_low_stock_pdf, name='export_low_stock_pdf'), + path('suppliers/', views.supplier_list, name='supplier_list'), + path('categories/', views.category_list, name='category_list'), + path('faktur/input/', views.input_faktur, name='input_faktur'), + path('faktur//', views.faktur_detail, name='faktur_detail'), + path('barang-keluar/', views.barang_keluar, name='barang_keluar'), + path('api/batches/', views.get_batches, name='get_batches'), + path('laporan/', views.laporan_transaksi, name='laporan_transaksi'), + path('transaksi//delete/', views.delete_transaksi, name='delete_transaksi'), + path('transaksi//edit/', views.edit_transaksi, name='edit_transaksi'), ] diff --git a/core/views.py b/core/views.py index c9aed12..2f67b54 100644 --- a/core/views.py +++ b/core/views.py @@ -1,25 +1,403 @@ import os import platform - +import io +from datetime import timedelta from django import get_version as django_version -from django.shortcuts import render +from django.shortcuts import render, redirect, get_object_or_404 from django.utils import timezone +from django.db.models import Sum, Q +from django.contrib.auth.decorators import login_required +from django.contrib import messages +from django.http import JsonResponse, HttpResponse +from .models import Medicine, Batch, Category, StockTransaction, Supplier, Faktur +from .forms import SupplierForm, FakturForm, StockInForm, StockOutForm, CategoryForm, MedicineForm +from reportlab.lib import colors +from reportlab.lib.pagesizes import letter, A4 +from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer +from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle +@login_required def home(request): - """Render the landing screen with loader and environment details.""" - host_name = request.get_host().lower() - agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic" + """Render the medicine warehouse dashboard.""" now = timezone.now() + today = now.date() + + # Stats + total_medicines = Medicine.objects.count() + + # Total stock across all batches + total_stock = Batch.objects.aggregate(total=Sum('quantity'))['total'] or 0 + + # Expired batches + expired_batches_count = Batch.objects.filter(expiry_date__lte=today).count() + + # Near expiry (next 90 days) + near_expiry_batches_count = Batch.objects.filter( + expiry_date__gt=today, + expiry_date__lte=today + timezone.timedelta(days=90) + ).count() + + # Low stock medicines + all_medicines = Medicine.objects.all() + low_stock_count = 0 + for med in all_medicines: + if med.total_stock <= med.min_stock: + low_stock_count += 1 + + # Latest medicines for the table + recent_medicines = all_medicines.order_by('-created_at')[:5] + + # Chart Data: Last 7 days movement + chart_labels = [] + chart_data_in = [] + chart_data_out = [] + + for i in range(6, -1, -1): + day = today - timedelta(days=i) + chart_labels.append(day.strftime('%d %b')) + + in_sum = StockTransaction.objects.filter( + transaction_type='IN', + created_at__date=day + ).aggregate(total=Sum('quantity'))['total'] or 0 + + out_sum = StockTransaction.objects.filter( + transaction_type='OUT', + created_at__date=day + ).aggregate(total=Sum('quantity'))['total'] or 0 + + chart_data_in.append(in_sum) + chart_data_out.append(out_sum) context = { - "project_name": "New Style", - "agent_brand": agent_brand, + "project_name": "DN-WRS", + "total_medicines": total_medicines, + "total_stock": total_stock, + "expired_count": expired_batches_count, + "near_expiry_count": near_expiry_batches_count, + "low_stock_count": low_stock_count, + "recent_medicines": recent_medicines, + "chart_labels": chart_labels, + "chart_data_in": chart_data_in, + "chart_data_out": chart_data_out, "django_version": django_version(), "python_version": platform.python_version(), "current_time": now, - "host_name": host_name, - "project_description": os.getenv("PROJECT_DESCRIPTION", ""), - "project_image_url": os.getenv("PROJECT_IMAGE_URL", ""), } return render(request, "core/index.html", context) + +# --- MASTER DATA MANAGEMENT --- + +@login_required +def category_list(request): + if request.method == 'POST': + form = CategoryForm(request.POST) + if form.is_valid(): + form.save() + messages.success(request, "Kategori berhasil ditambahkan.") + return redirect('category_list') + else: + form = CategoryForm() + + categories = Category.objects.all().order_by('name') + return render(request, 'core/categories.html', { + 'categories': categories, + 'form': form, + 'project_name': 'DN-WRS' + }) + +@login_required +def supplier_list(request): + if request.method == 'POST': + form = SupplierForm(request.POST) + if form.is_valid(): + form.save() + messages.success(request, "Supplier berhasil ditambahkan.") + return redirect('supplier_list') + else: + form = SupplierForm() + + suppliers = Supplier.objects.all().order_by('name') + return render(request, 'core/suppliers.html', { + 'suppliers': suppliers, + 'form': form, + 'project_name': 'DN-WRS' + }) + +@login_required +def medicine_list(request): + query = request.GET.get('q') + show_low_stock = request.GET.get('low_stock') == '1' + + if request.method == 'POST': + form = MedicineForm(request.POST) + if form.is_valid(): + form.save() + messages.success(request, "Data barang berhasil ditambahkan.") + return redirect('medicine_list') + else: + form = MedicineForm() + + medicines_all = Medicine.objects.all().order_by('name') + + # Calculate low stock count for the summary + low_stock_count = 0 + for med in medicines_all: + if med.total_stock <= med.min_stock: + low_stock_count += 1 + + medicines = medicines_all + + if query: + medicines = medicines.filter( + Q(name__icontains=query) | + Q(sku__icontains=query) | + Q(category__name__icontains=query) + ) + + if show_low_stock: + # Filter for low stock + low_stock_ids = [m.id for m in medicines if m.total_stock <= m.min_stock] + medicines = medicines.filter(id__in=low_stock_ids) + + return render(request, 'core/medicines.html', { + 'medicines': medicines, + 'form': form, + 'project_name': 'DN-WRS', + 'query': query, + 'show_low_stock': show_low_stock, + 'low_stock_count': low_stock_count, + 'total_count': medicines_all.count() + }) + +@login_required +def export_low_stock_pdf(request): + # Get low stock medicines + all_medicines = Medicine.objects.all().order_by('main_supplier__name', 'name') + low_stock_medicines = [m for m in all_medicines if m.total_stock <= m.min_stock] + + # Group by main supplier + grouped = {} + for m in low_stock_medicines: + supplier_name = m.main_supplier.name if m.main_supplier else "Tanpa Supplier Utama" + if supplier_name not in grouped: + grouped[supplier_name] = [] + grouped[supplier_name].append(m) + + # Generate PDF + buffer = io.BytesIO() + doc = SimpleDocTemplate(buffer, pagesize=A4) + elements = [] + styles = getSampleStyleSheet() + + title_style = ParagraphStyle( + 'Title', + parent=styles['Heading1'], + fontSize=18, + alignment=1, + spaceAfter=20, + textColor=colors.HexColor("#0d6efd") + ) + + elements.append(Paragraph("Laporan Daftar Stok Menipis", title_style)) + elements.append(Paragraph(f"Tanggal Cetak: {timezone.now().strftime('%d %B %Y %H:%M')}", styles["Normal"])) + elements.append(Spacer(1, 20)) + + if not low_stock_medicines: + elements.append(Paragraph("Tidak ada stok yang menipis saat ini.", styles["Normal"])) + else: + for supplier, items in grouped.items(): + elements.append(Paragraph(f"Supplier Utama: {supplier}", styles["Heading2"])) + elements.append(Spacer(1, 5)) + + data = [["Nama Barang", "SKU", "Stok", "Min.", "Satuan", "Supplier Alternatif"]] + for item in items: + alt_supplier = item.alternative_supplier.name if item.alternative_supplier else "-" + data.append([ + item.name, + item.sku, + str(item.total_stock), + str(item.min_stock), + item.unit, + alt_supplier + ]) + + table = Table(data, colWidths=[160, 60, 40, 40, 50, 110]) + table.setStyle(TableStyle([ + ('BACKGROUND', (0, 0), (-1, 0), colors.HexColor("#343a40")), + ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke), + ('ALIGN', (0, 0), (-1, -1), 'CENTER'), + ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), + ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), + ('FONTSIZE', (0, 0), (-1, 0), 10), + ('BOTTOMPADDING', (0, 0), (-1, 0), 10), + ('TOPPADDING', (0, 0), (-1, 0), 10), + ('GRID', (0, 0), (-1, -1), 0.5, colors.grey), + ('FONTSIZE', (0, 1), (-1, -1), 9), + ])) + elements.append(table) + elements.append(Spacer(1, 15)) + + doc.build(elements) + + buffer.seek(0) + response = HttpResponse(buffer, content_type='application/pdf') + response['Content-Disposition'] = 'attachment; filename="laporan_stok_menipis.pdf"' + return response + +# --- TRANSAKSI --- + +@login_required +def input_faktur(request): + if request.method == 'POST': + faktur_form = FakturForm(request.POST) + if faktur_form.is_valid(): + faktur = faktur_form.save() + return redirect('faktur_detail', pk=faktur.pk) + else: + faktur_form = FakturForm(initial={'faktur_type': 'MASUK', 'date': timezone.now().date()}) + + fakturs = Faktur.objects.all().order_by('-created_at') + return render(request, 'core/input_faktur.html', { + 'faktur_form': faktur_form, + 'fakturs': fakturs, + 'project_name': 'DN-WRS' + }) + +@login_required +def faktur_detail(request, pk): + faktur = get_object_or_404(Faktur, pk=pk) + if request.method == 'POST': + form = StockInForm(request.POST) + if form.is_valid(): + # Create Batch + batch = Batch.objects.create( + medicine=form.cleaned_data['medicine'], + faktur=faktur, + batch_number=form.cleaned_data['batch_number'], + expiry_date=form.cleaned_data['expiry_date'], + quantity=form.cleaned_data['quantity'], + buying_price=form.cleaned_data['buying_price'], + selling_price=form.cleaned_data['selling_price'] + ) + # Create Transaction + StockTransaction.objects.create( + medicine=batch.medicine, + batch=batch, + faktur=faktur, + transaction_type='IN', + quantity=batch.quantity, + note=f"Input dari Faktur {faktur.faktur_number}" + ) + messages.success(request, f"Barang {batch.medicine.name} berhasil ditambahkan.") + return redirect('faktur_detail', pk=pk) + else: + form = StockInForm() + + items = Batch.objects.filter(faktur=faktur) + return render(request, 'core/faktur_detail.html', { + 'faktur': faktur, + 'form': form, + 'items': items, + 'project_name': 'DN-WRS' + }) + +@login_required +def barang_keluar(request): + if request.method == 'POST': + form = StockOutForm(request.POST) + if form.is_valid(): + medicine = form.cleaned_data['medicine'] + batch = form.cleaned_data['batch'] + qty = form.cleaned_data['quantity'] + + if batch.quantity < qty: + messages.error(request, f"Stok tidak mencukupi. Stok saat ini: {batch.quantity}") + else: + # Update Batch + batch.quantity -= qty + batch.save() + + # Create Transaction + StockTransaction.objects.create( + medicine=medicine, + batch=batch, + transaction_type='OUT', + quantity=qty, + note=form.cleaned_data['note'] + ) + messages.success(request, f"Barang keluar berhasil dicatat.") + return redirect('barang_keluar') + else: + form = StockOutForm() + + transactions = StockTransaction.objects.filter(transaction_type='OUT').order_by('-created_at')[:10] + return render(request, 'core/barang_keluar.html', { + 'form': form, + 'transactions': transactions, + 'project_name': 'DN-WRS' + }) + +@login_required +def get_batches(request): + medicine_id = request.GET.get('medicine_id') + batches = Batch.objects.filter(medicine_id=medicine_id, quantity__gt=0).values('id', 'batch_number', 'quantity') + return JsonResponse(list(batches), safe=False) + +@login_required +def laporan_transaksi(request): + transactions = StockTransaction.objects.all().order_by('-created_at') + return render(request, 'core/laporan_transaksi.html', { + 'transactions': transactions, + 'project_name': 'DN-WRS' + }) + +@login_required +def delete_transaksi(request, pk): + transaction = get_object_or_404(StockTransaction, pk=pk) + batch = transaction.batch + + if batch: + if transaction.transaction_type == 'IN': + batch.quantity -= transaction.quantity + elif transaction.transaction_type == 'OUT': + batch.quantity += transaction.quantity + batch.save() + + transaction.delete() + messages.success(request, "Transaksi berhasil dihapus dan stok telah diperbarui.") + return redirect('laporan_transaksi') + +@login_required +def edit_transaksi(request, pk): + transaction = get_object_or_404(StockTransaction, pk=pk) + if request.method == 'POST': + new_qty = int(request.POST.get('quantity')) + old_qty = transaction.quantity + batch = transaction.batch + + if batch: + if transaction.transaction_type == 'IN': + # Revert old, apply new + batch.quantity = batch.quantity - old_qty + new_qty + elif transaction.transaction_type == 'OUT': + # Revert old, apply new + batch.quantity = batch.quantity + old_qty - new_qty + + if batch.quantity < 0: + messages.error(request, "Error: Stok tidak boleh negatif setelah perubahan.") + return redirect('laporan_transaksi') + + batch.save() + + transaction.quantity = new_qty + transaction.note = request.POST.get('note', transaction.note) + transaction.save() + messages.success(request, "Transaksi berhasil diperbarui.") + return redirect('laporan_transaksi') + + return render(request, 'core/edit_transaksi.html', { + 'transaction': transaction, + 'project_name': 'DN-WRS' + }) \ No newline at end of file diff --git a/db/config.php b/db/config.php index 1167971..777c778 100644 --- a/db/config.php +++ b/db/config.php @@ -1,9 +1,9 @@