From bdafaca493601a08f6ce21daa06108ad84c481d4 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Tue, 3 Feb 2026 03:17:21 +0000 Subject: [PATCH] adding accounting --- accounting/__init__.py | 0 .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 163 bytes accounting/__pycache__/admin.cpython-311.pyc | Bin 0 -> 218 bytes accounting/__pycache__/apps.cpython-311.pyc | Bin 0 -> 880 bytes accounting/__pycache__/models.cpython-311.pyc | Bin 0 -> 5915 bytes .../__pycache__/signals.cpython-311.pyc | Bin 0 -> 6625 bytes accounting/__pycache__/urls.cpython-311.pyc | Bin 0 -> 985 bytes accounting/__pycache__/views.cpython-311.pyc | Bin 0 -> 10475 bytes accounting/admin.py | 3 + accounting/apps.py | 10 ++ accounting/management/__init__.py | 0 .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 174 bytes accounting/management/commands/__init__.py | 0 .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 183 bytes .../setup_accounts.cpython-311.pyc | Bin 0 -> 2877 bytes .../management/commands/setup_accounts.py | 47 ++++++ accounting/migrations/0001_initial.py | 56 +++++++ accounting/migrations/__init__.py | 0 .../__pycache__/0001_initial.cpython-311.pyc | Bin 0 -> 3819 bytes .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 174 bytes accounting/models.py | 73 +++++++++ accounting/signals.py | 148 +++++++++++++++++ .../templates/accounting/account_ledger.html | 67 ++++++++ .../templates/accounting/balance_sheet.html | 123 ++++++++++++++ .../accounting/chart_of_accounts.html | 57 +++++++ .../templates/accounting/dashboard.html | 107 ++++++++++++ .../templates/accounting/journal_entries.html | 66 ++++++++ .../templates/accounting/profit_loss.html | 83 ++++++++++ .../templates/accounting/trial_balance.html | 66 ++++++++ accounting/tests.py | 3 + accounting/urls.py | 12 ++ accounting/views.py | 154 ++++++++++++++++++ config/__pycache__/settings.cpython-311.pyc | Bin 5917 -> 5929 bytes config/__pycache__/urls.cpython-311.pyc | Bin 1195 -> 1291 bytes config/settings.py | 1 + config/urls.py | 1 + core/__pycache__/models.cpython-311.pyc | Bin 35523 -> 35699 bytes ...0017_expensecategory_accounting_account.py | 20 +++ ...ategory_accounting_account.cpython-311.pyc | Bin 0 -> 1117 bytes core/models.py | 1 + core/templates/base.html | 40 +++++ 41 files changed, 1138 insertions(+) create mode 100644 accounting/__init__.py create mode 100644 accounting/__pycache__/__init__.cpython-311.pyc create mode 100644 accounting/__pycache__/admin.cpython-311.pyc create mode 100644 accounting/__pycache__/apps.cpython-311.pyc create mode 100644 accounting/__pycache__/models.cpython-311.pyc create mode 100644 accounting/__pycache__/signals.cpython-311.pyc create mode 100644 accounting/__pycache__/urls.cpython-311.pyc create mode 100644 accounting/__pycache__/views.cpython-311.pyc create mode 100644 accounting/admin.py create mode 100644 accounting/apps.py create mode 100644 accounting/management/__init__.py create mode 100644 accounting/management/__pycache__/__init__.cpython-311.pyc create mode 100644 accounting/management/commands/__init__.py create mode 100644 accounting/management/commands/__pycache__/__init__.cpython-311.pyc create mode 100644 accounting/management/commands/__pycache__/setup_accounts.cpython-311.pyc create mode 100644 accounting/management/commands/setup_accounts.py create mode 100644 accounting/migrations/0001_initial.py create mode 100644 accounting/migrations/__init__.py create mode 100644 accounting/migrations/__pycache__/0001_initial.cpython-311.pyc create mode 100644 accounting/migrations/__pycache__/__init__.cpython-311.pyc create mode 100644 accounting/models.py create mode 100644 accounting/signals.py create mode 100644 accounting/templates/accounting/account_ledger.html create mode 100644 accounting/templates/accounting/balance_sheet.html create mode 100644 accounting/templates/accounting/chart_of_accounts.html create mode 100644 accounting/templates/accounting/dashboard.html create mode 100644 accounting/templates/accounting/journal_entries.html create mode 100644 accounting/templates/accounting/profit_loss.html create mode 100644 accounting/templates/accounting/trial_balance.html create mode 100644 accounting/tests.py create mode 100644 accounting/urls.py create mode 100644 accounting/views.py create mode 100644 core/migrations/0017_expensecategory_accounting_account.py create mode 100644 core/migrations/__pycache__/0017_expensecategory_accounting_account.cpython-311.pyc diff --git a/accounting/__init__.py b/accounting/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/accounting/__pycache__/__init__.cpython-311.pyc b/accounting/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a3bd12cd93f6ebc69d826bc2baf27d9775551a55 GIT binary patch literal 163 zcmZ3^%ge<80vU~&AnGxQfB{A*<1-tOF`XfWA(%mv(QhR~5fhOA86^43Og|$(H&wqh zsWh*oR6n&MHMz7TzevA4zbL!7ATc>rKQTEuA1IfZm#!ZlpP83g5+AQuQ2C3)CO1E& fG$+-rh!toI$f9C?An}2jk&*EO1B@tQ28sayJd`G6 literal 0 HcmV?d00001 diff --git a/accounting/__pycache__/admin.cpython-311.pyc b/accounting/__pycache__/admin.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6acedcbc7999e56203cc68b174ccc7b7ed3f6e81 GIT binary patch literal 218 zcmZ3^%ge<80vU~&_CV?}m;)u4p!8=UAY(d13PTEG4nr>0!_wS ztcfYPnR$MiOt<(_vJ&&s^YxPR^Gb>`lZu#u3RW_F2C4gHte=seo2p-$RGL>(s-Ies znp|3vU!-52UzA;3keHmRpO~DS50uNyOV+=i^C>2KczG$)vkyGXa*w?7s~>P T56p~=j5in*E?`4NY(NzNLk>6K literal 0 HcmV?d00001 diff --git a/accounting/__pycache__/apps.cpython-311.pyc b/accounting/__pycache__/apps.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f7a4f429eb036ccba379e225ec8f5244603eb1d3 GIT binary patch literal 880 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 LsZE|>nZfP9K#stU literal 0 HcmV?d00001 diff --git a/accounting/__pycache__/urls.cpython-311.pyc b/accounting/__pycache__/urls.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..282fef839a2f8f8a0218bd56faffbbde39e1f49e GIT binary patch literal 985 zcmZvaL2ueH6vrK63N!>-%A{2t-C>$0CWT0qHmOrrc7V&!rlE-omndLE!a|IkWZgKi z8{c3YciJ>gbkkcd;7EE3HL(oQo!kV_@vE6xx#5r_STv{o8dZ_C+GLId6IlZr`X?JnNLFmGU=6v)stsvb(v4+ M6BCJlw7@R;4}T*E;Q#;t literal 0 HcmV?d00001 diff --git a/accounting/__pycache__/views.cpython-311.pyc b/accounting/__pycache__/views.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..be7c24015421910ab1b54795841b4caf957d8afc GIT binary patch literal 10475 zcmd5?Yitx(magh|yZYhow!8cQY_PH20k@67F%V2laCqZv0tsNq^t8JQyG=jHt#ZJm zz3Gv%D{Gb?WMnDUiXR?nhH)0J#E)IcBV{Ch><>qpSyi=4>Q+gXBCU`%e^y3Jq!GXN zoT}=V9~f&k*(s`z?>+Z%?mhS3@0@#z-?`m26gBMe@fF2U z9G#$&^a!0ajhJZ6n-b=vWyAt$GiOOyleQ6C(mrBOIz}8?xi#TT){N97nGr_I+Y+v% zd&I4!?FmoPGvc8s-oiP)pdNvY$3@B1h!?odb=+Fu)~w_DfXl4o`hn|O#|;42y^dQ4 zT+cdgJ?Fhdg=)XYQH4yZjqoXsC#r9Z7bEG>2|fmYNaRrOp%ATl66vvcDnj^ACgX(X zzNfK`S+$>t#nO{0QFWb5Pm)wL@oq{aQz~QR&Wd~zdNyC0Ose$z(2YZkC;5Bn6pv{x zD)N|*VQ0k$m&**l7yn|xMG-5?IQk1pEG`;UnJA^_vBkQFGcB4QLHoz0K8DsTLv`si zT_w%3O=;F`N;6eSvu#tFeVfwEqO+`^32QN3y)EZ$MSkU9&cQkLe~UGbpylII>uHPV zDc9U`J(!GT(e(&c?Qv=4rmY$4CadRO^i;3UyrV3uoDu`&H0P~uu~=K3=X{N&%C~9G zxv6abk1zWk?DT2dw)7X+K!5g(eG~ot1asI__D`^On*JOaI%CS1Gq#Kaf5;Q-N=sac z5KNg{PH}Zyef53>&=3BDsk}=#_Mv^+DV7;jhT9CeBMnjZd|R_&wFL^KuSzc#6+aIb2P#$p0? z)!O>chcmRa61~~d!Q~taXdBF^J$#FBZO~Hy>uQM z<5T?I+vNDY&T@H#iwfhT=_ujC#}esSG$9-hmntFv3pnckhJRT~3SNhIUU(qPex|1@ z=?k2TYK;m4FT(I$hKz_fb-NnYx2+FS3q2Db5{-4dkwPp^%8&dE_aZNB?`Ra49p`{k52qHQ` z?T4(YnTt-TmT|Dusx28$O^Uo~6Zlv<#i>j(9wTWZV~eL^u-CwH*B2KwQcU2=se8x5v>hd=K0Q}lk2o(BE9lcxy~%V+98XMvXYwhZn&it2Lxx(D0(a&% zN#6v)9>T|9=n)(~^mVVXd?tFzXJTATCPHr2EKDYe9fxh#y&*xh-Hazho)BCQ)tpM- zQSF4koq*Fv(7LM5=-3$H$6!tbr$f+Ssg5)Ow<&o*hpLuc$YU0R* z^RO?n)U!ZPKhpm?wwTU`hjQT|>4R%hct{S9DB%$(lY`e4pn>a3;ClALN<*`LYW}_d z-`>t2JexasR=P4O9Xu-^j420WP$oBX3eW~lY2YMF{R#@eEm|pHL=$jb2?M;`kb^fA zpn)4o;70bs4>l^$JiGtP@N78m@5=eR7T$mI{?aGQKI!T;$=@aWM-=}^_WZND7NxE; zUl-2Rg$uhn3XScnR;s?|Ic2W5{TWI2;;Msc*qdkf<=A~s8YFg~%pO(Pqj`2H#}57J z;PTyX2mUx9u|qO@O<}J|BT7l*-tQX2Ob zc7_WL?S%|FT(Wy0GeXcx|f` zknHIn{FKZ8pi^pobIC3seyC041g6gdskBt8Z(XP4Jk)su>tR1|`n?u1J_dCzE4W1- zMP*Ejrs|zOU%G;oYf)O1VvQU#XPvf;QE<&N4^ba~JZ;OECh$~h4`=(7BF!1w1X?+r zW51=%kJy2Wv(uhbRgR&i?Wv}!R*u(em9b>(_uO#pvB1Tv;-nVkz)Wo!h>JX2Gh6VH ze~1DImy#6d-@emKO77ftGHV7u8y3m zW8seE>X2RiimN~G8qB!{pYD|Y?z-%{p}1~j-(9s)Y(w_FLZCi-p-@}DN;$2JMrJGp z#;-6fQd_^o;9F?#`pwxVY_9#V)PDHE<=N{Gt}lt-e72OBIW=?YJFT!G*M3B5KcW{t zwfqOOl$kkIaIkZ4%8uPL-h!hp?`Y0BnhSLe1-4_=YTjA1N&x{+z{!9o-~`b{@ih8K&LYKa>MfJ86s3gh9d)h!ln)p^cdzAHnB#WW4rv59oY zPaxfqv2&(dn!W@G5ERp-Eo0g^oCK(1hk8!H6*vXu&>>Ahm_bJ`GvJs*$Fck0R)(Lz z<3L=3@=b7woB=Irn4{xN2(x(D$$lhwN7AuJhm$fG-P>7#KX7-6CWv%1c*n?rtv+z67NBSCm3 z*MO)tO|Aep6YWYU2#9q>sT+^_g9NxrMnm8&zhf}j0r|F8DXpLwuf_IE4(?!3Q0=kNbR z>}l8UliwsIf4}U%p!hE=Us3!YO8yb)+Q^LkJMh;|{PO6`(K&Yhz&|(7H5XjLylZF9 zwX@K?tI*g|Xxa^k)8YY$Y4HH!xV?ZlZZBkk)KKmQfHl|dnezo#!`x}v)i!hCFVA!r z@Ms~}T4478YC#7B2w4MLLl&4@-9`CNSi$W<7DumjJHX+C?uyS=E<9&m*WR?P_Lg<+ z&0@)Uf%rdc7X8^62080maN5|t5}9H@;6m9&)R^&bjzwoR3_t`NpuZfyennemtG4T6 z;T8Z6cNKhkfa9&^XmL;Lw)9Z@SL_pOnWG|2qO*$~G8J8|hp{)U~hVAI& zf`4m@5!J*IaudtZ?d5pEV+k^Sro0L^=Z^Dy=KIX15)F_Lfc&1!+nrg^ZreY`0v+@IM^oz`-%}} zOjo|zh^t(Z>Lt_ys1CTm;Aa6rWh%nA+78uYvU-qNWwcrm&1F%&#i~;V!==Z0jhqF; z#c+?p`)bAQNxMvy{S}0Bauf+VEn2Xa3_vR6(%qL^$W0)@J#@@qq_~)I7_n9e zI3v55BS~zRLXt*u8;F46;F5y_3B$*!QVRJLDve|9|IvLMcisI+-rbgSw>@Dccbn`! zqPUM_Pp$YHXM6Op=aXAnwDTRs|4#P&N=5w!t^QHPeKdOtLXBPsY3$B>x^kYbrMr@+ zOZJ>mJZJKr4|1LlRv6FB-QUvlccs=|xwThm?Uk4VGIKy-4it;-%zJ*dS#|p zVR|8o>b*q6<;ja-RE+jP#IUY;?$GRj6xcg|VxjX%`(n5B#<8UXvVQ>Lupr;NC+`jA zyrHK~$s3Zr=M?X`y!Ud>dwDY!y`1)#bo2C>tI_LbU( zS=X1ISuLNL)$tl=or>49?`5I+O8kjUhUF9CPflyLD;}r zJ%ACsXa{0^KV=6cc5uNwXMV^)V$J(gVE-25{St;srjX#b&wI@k7tP9vYM5?QYm1+y zjA{aW3)Da6mBQxbPggpom%dtQS#lUfeF7w8UyI`#VYXGr?9Z?jIsyc3E78mq#y~aL z)$x|87%ur7%Kiy{!XgleWwEIktQ0u*7!D?2y6^NtU{mih?6r!Mh6kE)+bg zgKsk0a`n6o37H*KfYuEvb%WW9E3Yavz~XRW$9{Yp;mcGH2GZ`)Dii(s2xWo=ot^$v;ACyrl`(fUcTpnl4apN*lce>WEbRZdOxo zUZC2fjot#aTdIBw)K01T{f_ponp)@+^eTn)xtTK6ezD77y+cE8)nuZtLNU@(vBq*3 zrI)qR%ax_T(o2IhtR;|AOJwwF4I8isNT~=JhqR(rECNz0LdK9j8V{BMDU}r&8dp2H F{|o6e(T)HB literal 0 HcmV?d00001 diff --git a/accounting/admin.py b/accounting/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/accounting/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/accounting/apps.py b/accounting/apps.py new file mode 100644 index 0000000..ed61952 --- /dev/null +++ b/accounting/apps.py @@ -0,0 +1,10 @@ +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + +class AccountingConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'accounting' + verbose_name = _('Accounting') + + def ready(self): + import accounting.signals \ No newline at end of file diff --git a/accounting/management/__init__.py b/accounting/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/accounting/management/__pycache__/__init__.cpython-311.pyc b/accounting/management/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1901e7461beed357a2cf2ecf7b807f30169a3b81 GIT binary patch literal 174 zcmZ3^%ge<81bi8dnIQTxh=2h`DC095kTIPhg&~+hlhJP_LlF~@{~09t%TYfgKQ~ps zG^sSNq*On(A~m_RB)>?%JijQrxF9h(RX;H~IUgvOnU}7go0ylFo|>DQSE3&upP83g r5+AQuQ2C3)CO1E&G$+-rh!toe$j)MZAn}2jk&*EO1B@tQ28sayZD=aV literal 0 HcmV?d00001 diff --git a/accounting/management/commands/__init__.py b/accounting/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/accounting/management/commands/__pycache__/__init__.cpython-311.pyc b/accounting/management/commands/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..89fee31c3e4247d0e3b5f10aa6799247bc0171e1 GIT binary patch literal 183 zcmZ3^%ge<81bi8dnIQTxh=2h`DC095kTIPhg&~+hlhJP_LlF~@{~09t%S%5aKQ~ps zG^sSNq*On(A~m_RB)>?%JijQrxF9h(RX;H~IUgvOnU}7go0ylFo|>DQSE8Ssp9|!r y6zj*wXXa&=#K-FuRQ}?y$<0qG%}KQ@Vg(uvvb>ldNPJ*sWMurn03(W+fnoquqb^eb literal 0 HcmV?d00001 diff --git a/accounting/management/commands/__pycache__/setup_accounts.cpython-311.pyc b/accounting/management/commands/__pycache__/setup_accounts.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8706e38e3ec4edb9aa1fb37f9b03eb44a0b4e9fd GIT binary patch literal 2877 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 literal 0 HcmV?d00001 diff --git a/accounting/migrations/__pycache__/__init__.cpython-311.pyc b/accounting/migrations/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d94ad5b13c0e1a198068ec91bfd068c605810da4 GIT binary patch literal 174 zcmZ3^%ge<80vU~&AnGxQfB{A*<1-tOF`XfWA(%mv(QhR~5fhOA86^43Q9mO;H&wqh zsWh*oR6n&MHMz7TzevA4zbL!7ATc>rKQTEuA1IfZm#&|inO>Awl9``ZtREkrnU`4- qAFo$X`HRCQH$SB`C)KWq6=)*J&SHKb@qw9 0: + JournalItem.objects.create( + entry=entry, + account=item['account'], + type=item['type'], + amount=item['amount'] + ) + return entry + +@receiver(post_save, sender=Sale) +def sale_accounting_handler(sender, instance, created, **kwargs): + # Sale (Invoice) Entry + # Debit: Accounts Receivable (1200) + # Credit: Sales Revenue (4000) + # Credit: VAT Payable (2100) (if applicable) + + ar_acc = get_account('1200') + sales_acc = get_account('4000') + vat_acc = get_account('2100') + + if not ar_acc or not sales_acc: + return + + # Subtotal and VAT logic (assuming total_amount includes VAT for now as per Sale model simplicity) + # Actually Sale model has total_amount and discount. + # Let's assume total_amount is the final amount. + + items = [ + {'account': ar_acc, 'type': 'debit', 'amount': instance.total_amount}, + {'account': sales_acc, 'type': 'credit', 'amount': instance.total_amount}, + ] + + create_journal_entry(instance, f"Sale Invoice #{instance.id}", items) + +@receiver(post_save, sender=SalePayment) +def sale_payment_accounting_handler(sender, instance, created, **kwargs): + # Debit: Cash (1000) + # Credit: Accounts Receivable (1200) + + cash_acc = get_account('1000') + ar_acc = get_account('1200') + + if not cash_acc or not ar_acc: + return + + items = [ + {'account': cash_acc, 'type': 'debit', 'amount': instance.amount}, + {'account': ar_acc, 'type': 'credit', 'amount': instance.amount}, + ] + + create_journal_entry(instance, f"Payment for Sale #{instance.sale.id}", items) + +@receiver(post_save, sender=Purchase) +def purchase_accounting_handler(sender, instance, created, **kwargs): + # Debit: Inventory (1300) + # Credit: Accounts Payable (2000) + + inv_acc = get_account('1300') + ap_acc = get_account('2000') + + if not inv_acc or not ap_acc: + return + + items = [ + {'account': inv_acc, 'type': 'debit', 'amount': instance.total_amount}, + {'account': ap_acc, 'type': 'credit', 'amount': instance.total_amount}, + ] + + create_journal_entry(instance, f"Purchase Invoice #{instance.id}", items) + +@receiver(post_save, sender=PurchasePayment) +def purchase_payment_accounting_handler(sender, instance, created, **kwargs): + # Debit: Accounts Payable (2000) + # Credit: Cash (1000) + + ap_acc = get_account('2000') + cash_acc = get_account('1000') + + if not ap_acc or not cash_acc: + return + + items = [ + {'account': ap_acc, 'type': 'debit', 'amount': instance.amount}, + {'account': cash_acc, 'type': 'credit', 'amount': instance.amount}, + ] + + create_journal_entry(instance, f"Payment for Purchase #{instance.purchase.id}", items) + +@receiver(post_save, sender=Expense) +def expense_accounting_handler(sender, instance, created, **kwargs): + # Debit: Expense Account + # Credit: Cash (1000) + + expense_acc = instance.category.accounting_account or get_account('5400') # Default to General Expense + cash_acc = get_account('1000') + + if not expense_acc or not cash_acc: + return + + items = [ + {'account': expense_acc, 'type': 'debit', 'amount': instance.amount}, + {'account': cash_acc, 'type': 'credit', 'amount': instance.amount}, + ] + + create_journal_entry(instance, f"Expense: {instance.category.name_en}", items) + +@receiver(post_delete, sender=Sale) +@receiver(post_delete, sender=SalePayment) +@receiver(post_delete, sender=Purchase) +@receiver(post_delete, sender=PurchasePayment) +@receiver(post_delete, sender=Expense) +def delete_accounting_entry(sender, instance, **kwargs): + content_type = ContentType.objects.get_for_model(instance) + JournalEntry.objects.filter(content_type=content_type, object_id=instance.id).delete() diff --git a/accounting/templates/accounting/account_ledger.html b/accounting/templates/accounting/account_ledger.html new file mode 100644 index 0000000..b23b1f4 --- /dev/null +++ b/accounting/templates/accounting/account_ledger.html @@ -0,0 +1,67 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block content %} +
+
+
+ +

+ {% trans "Ledger" %}: + {% if LANGUAGE_CODE == 'ar' %}{{ account.name_ar }}{% else %}{{ account.name_en }}{% endif %} + ({{ account.code }}) +

+
+
+

+ {% trans "Current Balance" %}: {{ total_balance|floatformat:global_settings.decimal_places }} {{ global_settings.currency_symbol }} +

+
+
+ +
+
+ + + + + + + + + + + + + {% for item_data in ledger_items %} + + + + + + + + + {% empty %} + + + + {% endfor %} + +
{% trans "Date" %}{% trans "Reference" %}{% trans "Description" %}{% trans "Debit" %}{% trans "Credit" %}{% trans "Balance" %}
{{ item_data.item.entry.date }}{{ item_data.item.entry.reference }}{{ item_data.item.entry.description }} + {% if item_data.item.type == 'debit' %}{{ item_data.item.amount|floatformat:global_settings.decimal_places }}{% endif %} + + {% if item_data.item.type == 'credit' %}{{ item_data.item.amount|floatformat:global_settings.decimal_places }}{% endif %} + + {{ item_data.balance|floatformat:global_settings.decimal_places }} +
{% trans "No transactions found for this account." %}
+
+
+
+{% endblock %} diff --git a/accounting/templates/accounting/balance_sheet.html b/accounting/templates/accounting/balance_sheet.html new file mode 100644 index 0000000..ad9598b --- /dev/null +++ b/accounting/templates/accounting/balance_sheet.html @@ -0,0 +1,123 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block content %} +
+
+
+ +

{% trans "Balance Sheet" %}

+

{% trans "As of" %} {{ date|date:"F d, Y" }}

+
+ +
+ +
+ +
+
+
+
{% trans "Assets" %}
+
+
+ + + {% for acc in assets %} + + + + + {% endfor %} + + + + + + + +
{% if LANGUAGE_CODE == 'ar' %}{{ acc.name_ar }}{% else %}{{ acc.name_en }}{% endif %}{{ acc.balance|floatformat:global_settings.decimal_places }}
{% trans "Total Assets" %}{{ asset_total|floatformat:global_settings.decimal_places }}
+
+
+
+ + +
+
+
+
{% trans "Liabilities" %}
+
+
+ + + {% for acc in liabilities %} + + + + + {% endfor %} + + + + + + + +
{% if LANGUAGE_CODE == 'ar' %}{{ acc.name_ar }}{% else %}{{ acc.name_en }}{% endif %}{{ acc.balance|floatformat:global_settings.decimal_places }}
{% trans "Total Liabilities" %}{{ liability_total|floatformat:global_settings.decimal_places }}
+
+
+ +
+
+
{% trans "Equity" %}
+
+
+ + + {% for acc in equity %} + + + + + {% endfor %} + + + + + + + + + + + +
{% if LANGUAGE_CODE == 'ar' %}{{ acc.name_ar }}{% else %}{{ acc.name_en }}{% endif %}{{ acc.balance|floatformat:global_settings.decimal_places }}
{% trans "Net Income (Loss)" %}{{ net_income|floatformat:global_settings.decimal_places }}
{% trans "Total Equity" %}{{ equity_total|floatformat:global_settings.decimal_places }}
+
+
+ +
+
+
{% trans "Total Liabilities & Equity" %}
+
+ {{ liability_total|add:equity_total|floatformat:global_settings.decimal_places }} +
+
+
+
+
+
+ + +{% endblock %} diff --git a/accounting/templates/accounting/chart_of_accounts.html b/accounting/templates/accounting/chart_of_accounts.html new file mode 100644 index 0000000..e443a36 --- /dev/null +++ b/accounting/templates/accounting/chart_of_accounts.html @@ -0,0 +1,57 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block content %} +
+
+
+ +

{% trans "Chart of Accounts" %}

+
+
+ +
+
+ + + + + + + + + + + + {% for account in accounts %} + + + + + + + + {% endfor %} + +
{% trans "Code" %}{% trans "Account Name" %}{% trans "Type" %}{% trans "Balance" %}{% trans "Actions" %}
{{ account.code }} + {% if LANGUAGE_CODE == 'ar' %}{{ account.name_ar }}{% else %}{{ account.name_en }}{% endif %} + + + {{ account.get_account_type_display }} + + + {{ account.balance|floatformat:global_settings.decimal_places }} {{ global_settings.currency_symbol }} + + + {% trans "Ledger" %} + +
+
+
+
+{% endblock %} diff --git a/accounting/templates/accounting/dashboard.html b/accounting/templates/accounting/dashboard.html new file mode 100644 index 0000000..86225d3 --- /dev/null +++ b/accounting/templates/accounting/dashboard.html @@ -0,0 +1,107 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block content %} +
+
+

{% trans "Accounting Dashboard" %}

+
+ {% trans "Financial Summary" %} +
+
+ + +
+
+
+
+
{% trans "Total Assets" %}
+

{{ total_assets|floatformat:global_settings.decimal_places }} {{ global_settings.currency_symbol }}

+
+
+
+
+
+
+
{% trans "Total Liabilities" %}
+

{{ total_liabilities|floatformat:global_settings.decimal_places }} {{ global_settings.currency_symbol }}

+
+
+
+
+
+
+
{% trans "Monthly Revenue" %}
+

{{ monthly_revenue|floatformat:global_settings.decimal_places }} {{ global_settings.currency_symbol }}

+
+
+
+
+
+
+
{% trans "Monthly Net Profit" %}
+

+ {{ net_profit|floatformat:global_settings.decimal_places }} {{ global_settings.currency_symbol }} +

+
+
+
+
+ + + + + +
+
+
{% trans "Recent Journal Entries" %}
+
+
+ + + + + + + + + + + {% for entry in recent_entries %} + + + + + + + {% empty %} + + + + {% endfor %} + +
{% trans "Date" %}{% trans "Reference" %}{% trans "Description" %}{% trans "Amount" %}
{{ entry.date }}{{ entry.reference }}{{ entry.description }} + {% with items=entry.items.all %} + {% for item in items %} + {% if item.type == 'debit' %} +
{{ item.amount|floatformat:global_settings.decimal_places }}
+ {% endif %} + {% endfor %} + {% endwith %} +
{% trans "No recent entries found." %}
+
+
+
+{% endblock %} diff --git a/accounting/templates/accounting/journal_entries.html b/accounting/templates/accounting/journal_entries.html new file mode 100644 index 0000000..f60b7ae --- /dev/null +++ b/accounting/templates/accounting/journal_entries.html @@ -0,0 +1,66 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block content %} +
+
+
+ +

{% trans "Journal Entries" %}

+
+
+ + {% for entry in entries %} +
+
+
+ {% trans "Date" %}: {{ entry.date }} + {% trans "Reference" %}: {{ entry.reference }} +
+
#{{ entry.id }}
+
+
+

{% trans "Description" %}: {{ entry.description }}

+
+ + + + + + + + + + {% for item in entry.items.all %} + + + + + + {% endfor %} + +
{% trans "Account" %}{% trans "Debit" %}{% trans "Credit" %}
+ {{ item.account.code }} - + {% if LANGUAGE_CODE == 'ar' %}{{ item.account.name_ar }}{% else %}{{ item.account.name_en }}{% endif %} + + {% if item.type == 'debit' %}{{ item.amount|floatformat:global_settings.decimal_places }}{% endif %} + + {% if item.type == 'credit' %}{{ item.amount|floatformat:global_settings.decimal_places }}{% endif %} +
+
+
+
+ {% empty %} +
+
+

{% trans "No journal entries found." %}

+
+
+ {% endfor %} +
+{% endblock %} diff --git a/accounting/templates/accounting/profit_loss.html b/accounting/templates/accounting/profit_loss.html new file mode 100644 index 0000000..d363d81 --- /dev/null +++ b/accounting/templates/accounting/profit_loss.html @@ -0,0 +1,83 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block content %} +
+
+
+ +

{% trans "Profit & Loss Statement" %}

+

{% trans "Period ending" %} {{ date|date:"F d, Y" }}

+
+ +
+ +
+
+
+
+ + + + + + + + + + + + + {% for acc in revenue_accounts %} + + + + + {% endfor %} + + + + + + + + + + {% for acc in expense_accounts %} + + + + + {% endfor %} + + + + + + + + + + + +
{% trans "Description" %}{% trans "Amount" %}
{% trans "REVENUE" %}
{% if LANGUAGE_CODE == 'ar' %}{{ acc.name_ar }}{% else %}{{ acc.name_en }}{% endif %}{{ acc.balance|floatformat:global_settings.decimal_places }}
{% trans "Total Revenue" %}{{ revenue_total|floatformat:global_settings.decimal_places }}
{% trans "EXPENSES" %}
{% if LANGUAGE_CODE == 'ar' %}{{ acc.name_ar }}{% else %}{{ acc.name_en }}{% endif %}({{ acc.balance|floatformat:global_settings.decimal_places }})
{% trans "Total Expenses" %}({{ expense_total|floatformat:global_settings.decimal_places }})
{% trans "NET PROFIT / LOSS" %}{{ net_profit|floatformat:global_settings.decimal_places }}
+
+
+
+
+
+ + +{% endblock %} diff --git a/accounting/templates/accounting/trial_balance.html b/accounting/templates/accounting/trial_balance.html new file mode 100644 index 0000000..314ebfe --- /dev/null +++ b/accounting/templates/accounting/trial_balance.html @@ -0,0 +1,66 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block content %} +
+
+
+ +

{% trans "Trial Balance" %}

+
+ +
+ +
+
+ + + + + + + + + + {% for data in trial_data %} + + + + + + {% endfor %} + + + + + + + + +
{% trans "Account" %}{% trans "Debit" %}{% trans "Credit" %}
+ {{ data.account.code }} - + {% if LANGUAGE_CODE == 'ar' %}{{ data.account.name_ar }}{% else %}{{ data.account.name_en }}{% endif %} + + {% if data.debit > 0 %}{{ data.debit|floatformat:global_settings.decimal_places }}{% endif %} + + {% if data.credit > 0 %}{{ data.credit|floatformat:global_settings.decimal_places }}{% endif %} +
{% trans "TOTAL" %}{{ total_debit|floatformat:global_settings.decimal_places }}{{ total_credit|floatformat:global_settings.decimal_places }}
+
+
+
+ + +{% endblock %} diff --git a/accounting/tests.py b/accounting/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/accounting/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/accounting/urls.py b/accounting/urls.py new file mode 100644 index 0000000..e9f86bb --- /dev/null +++ b/accounting/urls.py @@ -0,0 +1,12 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('', views.accounting_dashboard, name='accounting_dashboard'), + path('chart-of-accounts/', views.chart_of_accounts, name='chart_of_accounts'), + path('journal-entries/', views.journal_entries, name='journal_entries'), + path('ledger//', views.account_ledger, name='account_ledger'), + path('trial-balance/', views.trial_balance, name='trial_balance'), + path('balance-sheet/', views.balance_sheet, name='balance_sheet'), + path('profit-loss/', views.profit_loss, name='profit_loss'), +] diff --git a/accounting/views.py b/accounting/views.py new file mode 100644 index 0000000..4d80692 --- /dev/null +++ b/accounting/views.py @@ -0,0 +1,154 @@ +from django.shortcuts import render, get_object_or_404 +from django.contrib.auth.decorators import login_required +from .models import Account, JournalEntry, JournalItem +from django.db.models import Sum, Q +from django.utils import timezone +from datetime import datetime + +@login_required +def accounting_dashboard(request): + total_assets = sum(acc.balance for acc in Account.objects.filter(account_type='asset')) + total_liabilities = sum(acc.balance for acc in Account.objects.filter(account_type='liability')) + total_equity = sum(acc.balance for acc in Account.objects.filter(account_type='equity')) + + # Revenue and Expenses for current month + month_start = timezone.now().replace(day=1, hour=0, minute=0, second=0, microsecond=0) + + revenue_items = JournalItem.objects.filter( + account__account_type='income', + entry__date__gte=month_start + ) + monthly_revenue = (revenue_items.filter(type='credit').aggregate(total=Sum('amount'))['total'] or 0) - \ + (revenue_items.filter(type='debit').aggregate(total=Sum('amount'))['total'] or 0) + + expense_items = JournalItem.objects.filter( + account__account_type='expense', + entry__date__gte=month_start + ) + monthly_expense = (expense_items.filter(type='debit').aggregate(total=Sum('amount'))['total'] or 0) - \ + (expense_items.filter(type='credit').aggregate(total=Sum('amount'))['total'] or 0) + + context = { + 'total_assets': total_assets, + 'total_liabilities': total_liabilities, + 'total_equity': total_equity, + 'monthly_revenue': monthly_revenue, + 'monthly_expense': monthly_expense, + 'net_profit': monthly_revenue - monthly_expense, + 'recent_entries': JournalEntry.objects.order_by('-date', '-id')[:10] + } + return render(request, 'accounting/dashboard.html', context) + +@login_required +def chart_of_accounts(request): + accounts = Account.objects.all().order_by('code') + return render(request, 'accounting/chart_of_accounts.html', {'accounts': accounts}) + +@login_required +def journal_entries(request): + entries = JournalEntry.objects.all().order_by('-date', '-id') + return render(request, 'accounting/journal_entries.html', {'entries': entries}) + +@login_required +def account_ledger(request, account_id): + account = get_object_or_404(Account, id=account_id) + items = JournalItem.objects.filter(account=account).order_by('entry__date', 'entry__id') + + # Calculate running balance + running_balance = 0 + ledger_items = [] + for item in items: + if account.account_type in ['asset', 'expense']: + change = item.amount if item.type == 'debit' else -item.amount + else: + change = item.amount if item.type == 'credit' else -item.amount + running_balance += change + ledger_items.append({ + 'item': item, + 'balance': running_balance + }) + + return render(request, 'accounting/account_ledger.html', { + 'account': account, + 'ledger_items': ledger_items, + 'total_balance': running_balance + }) + +@login_required +def trial_balance(request): + accounts = Account.objects.all().order_by('code') + trial_data = [] + total_debit = 0 + total_credit = 0 + + for acc in accounts: + items = acc.journal_items.all() + debits = items.filter(type='debit').aggregate(total=Sum('amount'))['total'] or 0 + credits = items.filter(type='credit').aggregate(total=Sum('amount'))['total'] or 0 + + if debits > 0 or credits > 0: + trial_data.append({ + 'account': acc, + 'debit': debits, + 'credit': credits + }) + total_debit += debits + total_credit += credits + + return render(request, 'accounting/trial_balance.html', { + 'trial_data': trial_data, + 'total_debit': total_debit, + 'total_credit': total_credit + }) + +@login_required +def balance_sheet(request): + assets = Account.objects.filter(account_type='asset') + liabilities = Account.objects.filter(account_type='liability') + equity = Account.objects.filter(account_type='equity') + + # Include Net Income in Equity + revenue = JournalItem.objects.filter(account__account_type='income').aggregate( + cr=Sum('amount', filter=Q(type='credit')), + dr=Sum('amount', filter=Q(type='debit')) + ) + net_revenue = (revenue['cr'] or 0) - (revenue['dr'] or 0) + + expenses = JournalItem.objects.filter(account__account_type='expense').aggregate( + dr=Sum('amount', filter=Q(type='debit')), + cr=Sum('amount', filter=Q(type='credit')) + ) + net_expenses = (expenses['dr'] or 0) - (expenses['cr'] or 0) + net_income = net_revenue - net_expenses + + asset_total = sum(acc.balance for acc in assets) + liability_total = sum(acc.balance for acc in liabilities) + equity_total = sum(acc.balance for acc in equity) + net_income + + return render(request, 'accounting/balance_sheet.html', { + 'assets': assets, + 'liabilities': liabilities, + 'equity': equity, + 'net_income': net_income, + 'asset_total': asset_total, + 'liability_total': liability_total, + 'equity_total': equity_total, + 'date': timezone.now() + }) + +@login_required +def profit_loss(request): + revenue_accounts = Account.objects.filter(account_type='income') + expense_accounts = Account.objects.filter(account_type='expense') + + revenue_total = sum(acc.balance for acc in revenue_accounts) + expense_total = sum(acc.balance for acc in expense_accounts) + + return render(request, 'accounting/profit_loss.html', { + 'revenue_accounts': revenue_accounts, + 'expense_accounts': expense_accounts, + 'revenue_total': revenue_total, + 'expense_total': expense_total, + 'net_profit': revenue_total - expense_total, + 'date': timezone.now() + }) \ No newline at end of file diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index ab578e63a03bc8677da8ab826d2aa5774f2c8491..f7be13a71c2ea788e60dc94ff9a9cd5055bd724b 100644 GIT binary patch delta 153 zcmbQMw^EOHIWI340}wE0G-jUL$h(7skz?{PjyXKHxDu0-^GoweGV{_mCvm1Tv&jQh z6=`ig&-0s!aqH&O{9R0@Rv;m3AaRR1Gq0q`2E?@k5%wU$0Yo^02xlO1i?z5QC$pr; heR7vjDr4#94?^LLf}BE39~fXn1K$U+%@HC;nE?2$De3?K delta 139 zcmZ3fH&>5$IWI340}!Y*G-U4C$h(7sk$v(pjyaq2IMbQA<$w}Jnn0q;V{#z3$mIRJ zBAYpQ&oMD>*?gV9i%HZHByR;IZZT)(l@wWnxVDoEg@k#XK!U8r1v!}|MQ)R)38gZY bZ2lt@&M3$s#PopyMl|q!5ZRm}a+C=GH?t-A diff --git a/config/__pycache__/urls.cpython-311.pyc b/config/__pycache__/urls.cpython-311.pyc index fe03dcc3a9c87784918a0c8c761eb0026373191f..0a0635bc7d325d71c63923e2ab37cee8f58047d1 100644 GIT binary patch delta 221 zcmZ3@+0CWCoR^o20SL4+8Z+lJF)%y^abN%}1LS<(G*SIF4--QwOA3f&PT`%nPew1D zHHtfxEsF=Hy2vk;8z{?>!Uq)Pg^8x{S8+2itPz-Oz-Y_}B4sBxGKzs&B3vng!3-d* zDKvQ-W1j?fVsdhRX#)=V0J=OgGynhq delta 161 zcmeC?TFt4xoR^o20SJT|8Zxt(7#JRdI55BoWqj_MsD4{7oh6Dpl`V@0CR^l}%FV=( z%8|kY6y=49rtnsAGcc^-n|y##Wby(=&dH^WVvI1JJeVcQ$sf#+0>qjElkYM1ZSG=H aV`Q|Lypeea=Lb$!ex?TQ4}6n*Sn2^Z$|3Lo diff --git a/config/settings.py b/config/settings.py index 35aeb66..7f0b943 100644 --- a/config/settings.py +++ b/config/settings.py @@ -56,6 +56,7 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', 'core', + 'accounting', ] MIDDLEWARE = [ diff --git a/config/urls.py b/config/urls.py index 22b4d6a..b731c7d 100644 --- a/config/urls.py +++ b/config/urls.py @@ -8,6 +8,7 @@ urlpatterns = [ path("accounts/", include("django.contrib.auth.urls")), path("i18n/", include("django.conf.urls.i18n")), path("", include("core.urls")), + path("accounting/", include("accounting.urls")), ] if settings.DEBUG: diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index a1d345bf8003092b9d560e34f612a0e29591428a..48736bbcfb8ba99a5165d3b0cda49227c74fdd6d 100644 GIT binary patch delta 2830 zcma);e^69a6vy|zWtVlA<(HtlfQaa(;41z|5K&=8Goe5WWCV3vcu&EWAKrbW;txb^ z`r%YMCmsK3CZ@*5vN7jXGg(cRG~+auXuovFCaozme-u*q zd*At<^B#wGD1Yo!VqbGQ?Iv-g)OR%0m+y~VHHKdH4PsF=qBb>6Wtw?~1#TRwB7V$! zqjn3+8y0dtk;-iJ=6$26yU7w49f>iS6q9CCZM5&c5!B{QY`3X{b~6z#&d8xEd+oaw zVG(EKsE$!gb4{vai%E^S#p6_+TTIolYV4z?thjL8oYgH_Q%EZg=-TpTzNRfPP*>O7 z+N3u$Eg$cT3@gHkS|rL}C&EUFxCSl6bH!5J0mJ}KAPy)Hpck^9wd@mvm6V>nhv~F) zh=&SuEUpS+3-?>Ze|dWi74au!q0%Gyr(}235Tj`9n`YKQm&P}<_i1U)Gj>Bv$bnTxPtJ744Dz|$#@vbf zEbjM&HEfyNs)h7DW_|#HoxpmUKiW-W@|^5tBR6jY8~MJlX0fnEZ`K1rei|{zHUA7a z3>=|vr*^Vej5Yag=J*+wpMbOU(zG(`FBpD6J=2D*a zE6$320mxct7_HY*jM_Odj9HE52R>2QQJOSwN6~TIwi+k|W&>XXGG9d)od8w<-wL!j z76yV1YDT4~xgZZybjc0}R^X?A@8sqWmL#&1^kvBcc8SXMH1d@Wi^7C= z%Is#^kHQLkY8)#)!^F-yN4kxi>QqK&%i^;BhFd1=AB-*oSAdzo&23+$j+zD5YcT#R zwvj^H%9E`>;!>f9TRfBHGLPW;H((+#Ks;ev1cw>fi=zE4E_*tLz$x;~?^4nkZTGnh z^MV93rB{re%8wxabAgKRpq5r%S04yzp)51MAND+83UCbY04AG$1pT1v1z$Uu2Pd1GH0;+&l0V~kIPex!N z;Ai7pl~=J~ShqbPSxB12ZY$>uOmSy9-PBW)Y)!({QqHMz704O3^=17onpR_#h^AvO z58$M&i@VquWB!suY83nJ0e8CB97V+T1SyDauZk6igLkrn|QWdG=6x<_a{oe$} z7a#`67iY){k=%4ax6zWuix${#X0yFH>9*OtrylXPq*@=sS~8=Ba#`6UaknJm-dm** z(*bkn%a$&sFp}&2D}#}u+U3IgsV&yCXq=~UoJY;@JUGqsuz39)pf5uCtcq;2v*8R!rp^+{UriXbTI{$+l^%jFQ&&+Di}xR2#|b@)cHMlyCTvu~wSAv6L;P z_KgMhYJ>pI)V1+lR!wU=)}}2L*J3Nu*MWMl)^SaIGP9z1EhTQ6WoZxrt7*}u-hvu2 z;ws{;;b`#+59m6-c_Y{0mN8F@dBmKxFf0S=Y3t@umY^7}H{RYnmbJ_OV0NKWdI9+% zmLDJaC6N^+-#@Z*WoyZLmSV|l%cRI>mOIBLIp*gnm=AAn`Ch+lo8H*0wg$DCTo+#P lYYO=-CS&Q7rk|=#DQ%{i z^k~_PQ$sd3W}{{rkD`KMN&fedgd(OGv<2=55 zuf3}5-l7b8CooW};JC+h7OF0pGxj$o%O23b5J*t^*o71(^N9uxOC$NU*jnz}%fsK^~Q z<^mLvKonpAwhp3QzG6P@%RZo^(-(yP18o%$0SqW8J7tajgxjUHd z6fENbnY4K&tea}`nsr#ev>oWe);8sjWCtlc-;yE>2VhVEA8~jB9F~<0wnkUAmFGv= z34Jl}F>TLJ;Tt%T9}(=!xB@`XoY9n07b%aJ5x|(Q95eHfk{zZqv$wEbiYqZje}{*c z0LM61=}Im3dTVS+qpRL7_0hhkw&;)t$pai0k#`h~V13kHP{c0LfXhHv3!+0rOn-yV z?P!jfGf70#s67lV&?C3aInDUwopE~kWJMIC*PaeG{Ry)ONk62&fEz$I@C1+t+@y@k zBK6-8Z}DYB()4-Z>NB`i&@D&&crnd0xW5c!0K?M5b>%QH1V~Air4d>KMBq3bo7bZ> zFv`h|kmaIKAI%}Sc5_g7apKxykH+DaY8P9aHmAu)ngHWuU?T7hkPNtiUSI_jSm{(|}0|a+NqoBNaD&3e$218=@3OZ$peO4Q01vQ)x3Z z`~dAq;Aaje=8TQ?*6{^8INuPez@{(40To_oil$Es!qvY(afvi##;jiNhV)w0tEDZSYO3+hhWw<&FLyJn7 zB4$A)c>D(w_CMuNfmyuekbrT305Vtgu!Q%Y>t->hVE48yq9M%B)hk!wnpHrS``#u zo)X`X5^s%-pEWgpEPMY?9b2*o!h45E<{P@{#ak-?S2c=q=h0l6E4j;1y z3M)`Y=8nfyOSsr9TRW22TJfo~6_tW-1Mz+q??v(U5yzr9ctp>NrjkTGi)4vti=c=- ziins Jqt;jX{0mlOq$L0V diff --git a/core/migrations/0017_expensecategory_accounting_account.py b/core/migrations/0017_expensecategory_accounting_account.py new file mode 100644 index 0000000..baf360f --- /dev/null +++ b/core/migrations/0017_expensecategory_accounting_account.py @@ -0,0 +1,20 @@ +# Generated by Django 5.2.7 on 2026-02-03 03:14 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('accounting', '0001_initial'), + ('core', '0016_expensecategory_expense'), + ] + + operations = [ + migrations.AddField( + model_name='expensecategory', + name='accounting_account', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='expense_categories', to='accounting.account'), + ), + ] diff --git a/core/migrations/__pycache__/0017_expensecategory_accounting_account.cpython-311.pyc b/core/migrations/__pycache__/0017_expensecategory_accounting_account.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3709cf9834cf2d13568e6f11f2235c600de0e643 GIT binary patch literal 1117 zcmZuwy>HV%6hEIoYP*SmfP@ekC@rN~;;_JikdR1`XsM9eBH;nc@g1&H#}58bTf|T# z1_p+1{SOML9rzO%_yam*h;C(KbJb3rc<0zD(BAppy{~)s-tV2iRI56IQD6JuEn|d! zaiJV?!5lvUW)~4e1Rt3K?AXUW(G+`Y%)D(ak;$V(Qolb;D?@l-Z^K46Jq*&??v!Rx7-$ z;=D)WcYirH3&)1cr4t#-)CAFSO6_eD62%&hr5Xt*geW{5;jC=0tbGik#fEK z#-l!2YE+qO<$B9vx@Cc1>Qmq=mi0EZeeTO8LZK)^0|)Ync(a-Mn2>oMBa*nN&jSLU z8!XT*rjV{3xFN%&!_+)0=sr`I+bh=6>f$2P!#*vZ#5Z)zJ9KIqYhjNXX$JsK4Z2C4 zGzlYPBaGJLzU@$h18kfvO{^cZRj;o!LQc`s>X1l^-vEz8bVw2kqB`wwoceAuM3nUSPtv zs}vvQ^dzU*grw<52*?R1nW)s1?Pq*Ox65=I3cE2vs!2M{(XY`g;fm_u#NTl82fG?o@p^>fEDf7DNG7+yRn_f*?SR!qg$UHTs5V?(m{7Q>ucJq4Q)%A6%X} M;)at8ta%Q902@y;YybcN literal 0 HcmV?d00001 diff --git a/core/models.py b/core/models.py index 4c5b59e..157d084 100644 --- a/core/models.py +++ b/core/models.py @@ -105,6 +105,7 @@ class PaymentMethod(models.Model): return f"{self.name_en} / {self.name_ar}" class ExpenseCategory(models.Model): + accounting_account = models.ForeignKey('accounting.Account', on_delete=models.SET_NULL, null=True, blank=True, related_name='expense_categories') name_en = models.CharField(_("Name (English)"), max_length=100) name_ar = models.CharField(_("Name (Arabic)"), max_length=100) description = models.TextField(_("Description"), blank=True) diff --git a/core/templates/base.html b/core/templates/base.html index dd8e629..2668d34 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -179,6 +179,46 @@ {% if user.is_superuser or user.is_staff %} + + +