From 5391ba10105226a17b51553b5a910cac32c54631 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Mon, 2 Feb 2026 09:36:33 +0000 Subject: [PATCH] Autosave: 20260202-093633 --- core/__pycache__/admin.cpython-311.pyc | Bin 2968 -> 3678 bytes core/__pycache__/models.cpython-311.pyc | Bin 7688 -> 16831 bytes core/__pycache__/urls.cpython-311.pyc | Bin 1442 -> 4025 bytes core/__pycache__/views.cpython-311.pyc | Bin 14812 -> 34935 bytes core/admin.py | 14 +- ...ng_logo_url_systemsetting_logo_and_more.py | 67 ++ core/migrations/0004_unit_product_unit.py | 28 + ...t_cost_price_product_is_active_and_more.py | 59 ++ ..._balance_due_purchase_due_date_and_more.py | 82 +++ ...e_due_date_sale_invoice_number_and_more.py | 91 +++ ...ystemsetting_logo_and_more.cpython-311.pyc | Bin 0 -> 2486 bytes .../0004_unit_product_unit.cpython-311.pyc | Bin 0 -> 1623 bytes ...product_is_active_and_more.cpython-311.pyc | Bin 0 -> 2495 bytes ...purchase_due_date_and_more.cpython-311.pyc | Bin 0 -> 4370 bytes ...le_invoice_number_and_more.cpython-311.pyc | Bin 0 -> 4242 bytes core/models.py | 143 +++- core/templates/base.html | 9 +- core/templates/core/customers.html | 130 +++- core/templates/core/index.html | 6 +- core/templates/core/inventory.html | 670 +++++++++++++++--- core/templates/core/invoice_create.html | 287 ++++++++ core/templates/core/invoice_detail.html | 195 +++++ core/templates/core/invoices.html | 160 +++++ core/templates/core/pos.html | 93 ++- core/templates/core/purchase_create.html | 275 +++++++ core/templates/core/purchase_detail.html | 182 +++++ core/templates/core/purchases.html | 168 +++-- core/templates/core/reports.html | 8 +- core/templates/core/settings.html | 27 +- core/templates/core/suppliers.html | 114 ++- core/urls.py | 37 +- core/views.py | 497 +++++++++++-- media/business_logos/albidar-logo.jpg | Bin 0 -> 102209 bytes 33 files changed, 3038 insertions(+), 304 deletions(-) create mode 100644 core/migrations/0003_remove_systemsetting_logo_url_systemsetting_logo_and_more.py create mode 100644 core/migrations/0004_unit_product_unit.py create mode 100644 core/migrations/0005_product_cost_price_product_is_active_and_more.py create mode 100644 core/migrations/0006_purchase_balance_due_purchase_due_date_and_more.py create mode 100644 core/migrations/0007_sale_balance_due_sale_due_date_sale_invoice_number_and_more.py create mode 100644 core/migrations/__pycache__/0003_remove_systemsetting_logo_url_systemsetting_logo_and_more.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0004_unit_product_unit.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0005_product_cost_price_product_is_active_and_more.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0006_purchase_balance_due_purchase_due_date_and_more.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0007_sale_balance_due_sale_due_date_sale_invoice_number_and_more.cpython-311.pyc create mode 100644 core/templates/core/invoice_create.html create mode 100644 core/templates/core/invoice_detail.html create mode 100644 core/templates/core/invoices.html create mode 100644 core/templates/core/purchase_create.html create mode 100644 core/templates/core/purchase_detail.html create mode 100644 media/business_logos/albidar-logo.jpg diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index 5a870f576c826e1df62d5a613df08e6ffc9766eb..7a79afc922699ff9070607e1a126e0877efd747c 100644 GIT binary patch literal 3678 zcmbtWJ8#=o6uy_FBubWK$CeyBX&eVl+^UV^xN$K_q8UIyN$KO`p8N4UzI(_&Yqbi2 z?XRDE)}TPhKX{RB#rWj-L?YxhF^NfS;!urJVoIiLOOC9`j-n|}K`S^#t>~1rlA~&> zQ`X8(MXNYd+7u-cVf1-5A!YI#O2|9-N1H_M6H|Fb%mP@|lDltYuz`QHy@A8K#EjD!#zGFG3XJ868D}z##T>>8FiuU(crL?O%3-VmV{Kx_*)aEYm@^G? z=8^=D=U!`Mo~!0OcLq4lP5j(uhOv^vI17w(6En_d7^iX=8^G9{nDM+hzeQT-KVVW? zR1^%;u{s}6wXL#H?-@SZarvH5o^~u>l%8n1fK6Yj0<%u=yq)j&Xg^~W^fJo zz-Nw7p9H+UYj{l5w)Q-@v&DSh>g=?W{NoW8T(})yfWvFTh(=AKNhZ}~TzVGO6jK2Q z3!;|z@&kI&`=i@U3b8Jo9Vi!>iRE% zVaFp?+wy$fw7jlu?1{N9XI-}&*buy_KeL!^dT+@w`uXqb+Af5>7HkI{KUiZgSv&Au zzV@5TUwB=k&DPp3XKP^+Z*=$gG|UM>ffH{TpqK9N=hl0V1{bgVarfZDR|m@Z=*Jh6 zDPws`+Q>0Rm(3I8M;2wQP@J1EczzB{MaA27xgVxr^R2>B{JeqCM3_fdKnNd;Mc|(# zP=*r^Cye@hyY~q9$R17e-OMW|lv36TaXG88lp>EyBOc4%i$D~*+-fsX2bHy7V5vI3 z{0iLIY!&mu6;olcpINpKqOEz%0F}m7&Zc4+$q}$$ zdzS!4Q=vooW>T@7;KUnGDILpg1X9AoZj(J?Pk0hj9gH zax^jpw?zRO1+yXRFMV!gU0nwYj)3ydWOGn$^fm_7T5kjQNDye6?=QwWE(oUVoO)bF zkE47lLb6Pdf6{iVzUv#dZa6M9B2j5`7B(xx&xt5p~)sZr?wqA;F38b{U1lAk5-$l1yMJj8=W2Z1}-mdZF3p!i()8R zkHhCJ;rLC@hg>MQxhs@JMi*@R#QdL>Iw56MlxQIf7QFuOZPi|ANcS+=KT$C{h>@L9gyXN@!x>#9{j)G zdpg}~zFOK>59wl`E)FG$t`Ess*x%?kt{&mQ3Br*~qzkFNN~D>PMLDF+K5Y&qO79HG zS=cZ1XO@p}-~<6#=2QD(!csb<3w^pUlw|t#ker46oBg>@k8t1w0a?KJ*uIjmj6}48 z5oIBbXayr$8I5RVm`0ScPITImY)~Y-oI5S-BL4xGhyG~* delta 1060 zcmb7D&1(}u6rat;7O<_PtKd&T-JJUlKgn@&HKIIyf>5Y`M2Zxp{B(N zU*Bp2`(tFN=cK#C@ZtRx$xX>=Z(Z6Vg6G$u=`}Vr8Ka zHWP}g-fHydPMbZ0+u^J5D!iqpxRNC?9Ztggvc?^z!ShH(9{0=-kqPKT7gU+)7%<>4 zYF*&@Q%DQ@LEJgRp=QY}jZOw3i(rBx7beEg6cux5O^zT2E3%o}7(6)6Ob_lKn~Q^0 z{CKI8fZ|VLQ}9lnyKxpdvm9zx^e++IDAdyhJlb=xgpFLJ70K&p&V2qqjUc~7eBcc8;BrF<ML3BSlj8Extyv~~iLW@W%IJ>W?NR2& zS8)j9BA9rA;(ds_!-2ZmTH|rwX|Q{uFEJ6(hbSM={jDzZk@g*Woh@@N{*$_M@H&yd bRR2o;`wKr3wNEdSFD0FXq{>eO@izYe)`!&L diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 783a3e3739cccdd5efb729071976f00cf08c4c90..040e7fa2f52f297d33163dc5d09f2a87c282e0c8 100644 GIT binary patch literal 16831 zcmeG@Yit`wdZhS%u1HFTMu zH_PXemVI|U95sH-*ELPi+2|UZ?v= z$LTnIT$j*?^?FR##p@D=u#u(>aZ|z^HtThDx;J&4@k1SF;`K9@LPeh#bh=OBQjf5e zVwnNUvJIAvVp##pwhfk@V%Y)9u??1kVmSfJwGEb&Vz~i}*#^r+vFZWKvkjJ;VtE0} zw+$9Uu^IrYaT~09iq!;IJGQ~{P^_JR)w~UsmtwU5R_iucK5o~TuD9(0zOY`sVoszu zJ}yAYInAfj{MB?M&StN{y)_+6@Yz(7kLF)4{VOugXK?$Gj@O0toGx5fr{fJX#sVVT znK=C?z*?Wer5;?}is6$w$bm~e!sZ%O#wSu)D3!HP3ujs}SJH%4_Oif0ST#?ybG8q4 zVFzc2A#fu~JIE$@f6uYxbUY?p=zYLJ?f3MGJ;7d$#QEfO`ofk=MHwXQ zR4m#X!lBC~W0x|O!&8_En_W;t{8c`hNvBBPl@z%s%(79wFPb8JpUMaaX0ItmYzJY06a|Q|8^B!g%r<@Bgm@c>o0WL8w)L>BF0oX@i}vqh_P}Y2EsPZ3Ny{7 z7{`;bG(pkv;CTQ9*_${4WbI=YQY7uiIsO21e>pxTtyu9c1nmIISe7zM)`{&nkZ09K zMB9S^Strq06A?75Xcw^2bjH_Yznrb88#O~b_1RA5R}QQ0+A4$iRR)Qklf7eI&Nt<*-BhR(3!*62tu zBY+A;VTp>-P}OrD&b#9K1jhN(;#h|pYEU**Q?4{>+Syoxu1Q0;gWFl-iA|Ie=S``t z%^FJYN{gn4J8Mw2K9Q=KQlU;MJ!_YSstx$du(De-F0GUj#X*U(T|;T8YGoUxLY+eo zsMlXf6K>}^K)Ct2Js{jV70XEi)ZjO(} z5^Ov&8waCAP%IpOp3TJ5Ae`Aauhwv{jvPhuR{>-#6N4dt&)(h#IF%n@jak<_vwSj^ zoc52UQ_+jigx4_FdOWFSWL^0|@*hvI(|nf64^;%7JIxDy&>~xAHlAX+2v|tieDW0l zig_@aj$P)#263`rJ4KSID-o9CDzpU71MJ#nH-L|H+gm$a-N!~W{({*$f6tb_7e*0H z48wb2Xc0@4&Moc$uRf4S>U5B%kS*8O>%(osvNDE2qN?u$()NBC>h zPEjm@!Lh*L@G-?2&0EG)eE>@w3q^wCqoaxuY+m3np}-=Y&;X5EA|u!lTL@eU)l715 zC8kiE!|F>`i|kS0jmRwWhGJ6x442uo(r_{*#BjuqC&5DJRZC29rPP-TMjyOf#ZCXv z4o;?H>1&FezEibg6MdnIb10RH^K4Shw8ex7OJAho8io#rz`-fBgYa~2u<4qbbU$_z z0UCA!xQQ_8g)_(a=P%2Qe|hKX#Qpch)4wJ%?@7#iGV|WtTRF!rw9$4ghVD+RMArK^ z(&AKHbR;B4LUttPhKsZ$7s}vukzh{i6LX$^MpXe`_v~^X^&bmc4tH55f7K)cF;;^DArpqW5*l`?~CX zeQqS@=|WZCwR{TB>$f9!BI{l;G%iNYi5^z+u(F4pdpqZBpMOJkwlC}9eC{@Lhgloi zxFkAHNzPNU^VHn2oV^WO(zeKg2VuJvUy83@+VEgK_6f;8A=@XQDGhxKZ^#XOt9m&5 zq=Q59!J&;ou^}im1m%X{+=*P{!G#IA@!+Zl&X<1BcE4>yFE+j{HNGu3zC9PrIoq%i z>N6N=_d$90LGhIn;`pR^juV}{14{fRMTrkV^oy&u8UbsDSXJoBGtbbLie^su3 zb?$i1vllzOceP*i9FaUnWX}=RqN!FNM-;B&QLr#J{}Zyl0HfIi2Czk=TQ%M? z=oAxp#o$AgdFJL7n?^%|2Msm=8p8CTR9cIpn##dBKp#0dCrCCIc-I-2pb4HRMl`F* z08~X4iDEi=0m2Fo(3%Z_K&R>hWvya5mSAIXas<^RT3={UL589eu!+3{8Or!>TobK3^8(XVOJJ56;c*}WiG}@8?6lQ+xx>>Q zoW^1cXb9{LtP70F2^?4=JLOUk}rVE>uAIWxS%ZTsImGfzgje0ZmmIQrSTw} z8V@3ep#uc_R^rvHJCI7IL239;@%ym)OHp!gL(OGwCKPuw6a6v(4a%ArQG>cl<1ObDXSbd z&R64^5CaFw%F$`jJhPQIf_uM9(^_4LyAd&3I&3?am#%? zc{v5fxj&dmOo2y;4j+|(Th34_&BpzMi4+8tA7HGQyo}&=09y{MIbTLSJ@N|89%8}G zgxZEPykZ@W2~i~8a-L+bCHQ38AG!uo3#IBk12qa1OY{OhU7%i^VjfGg>5Q;t1#&;y z?_fKicSij*#S~1X0TXQp@+N=^$!vrS5F5J9Apo%4g|TNysRzWO>&j!%#94kfQ&+%C z>Nx|%snKsU2D4`Bly+_z{QLci>CFr;qN0E5fN_5HnALN}uj;SEXhv{^=>cO`tQXS7{4Cg6fLDy& zL0K`)886Zn6423rQxNsKWJ`GsfJJKqEOP~xg|n33U)L>L3#}>t{{(J6EoR{2CfJPD zpkihz6T^y*DUD1`J_00_ZNG}CUq|o(fO|HGt*E>zC^mL_n())WhU$ueVJwqS95|IC zYJG~6)>gQ4OsUKt;eh$g0%c`bL0}=OP)GbwyW%d?4Y3DkhJcLc&#!d2GqW5#Dw3}x z>!bWrnRy2AHx~c^8#T8rwf$T3!JjrC{Nd&M+4Uajg%SCM5wZE8)I2ITk8ZpxH;;?W zVR14%@6NUM&KqUx&RlD|-1^+zwA}Za)cTs5EiNu#{^4i?%_oTwJO72Zz-$C^+h-jt+bC4%*wL(qZXy9hMPJ(n>L z!BMz`z3}$tis#R4%z_8bfp0e7Y=$+noXxXv2+lL#eEH_fqP0C|^DYd+8T;nw%~8?X z@z7%2WfqM)A3AmRdJSbIrnV-aY%5cmJ0*Ifk~b=QqYy)QN;GB%?ZM-g{E&IxKe`Uf&}Ir^GlBeS+i@WS;;nbG6M+0H@M`gZ0+MrHiXm>j%W}Inl*R zE>?E2b8qCBc6^>1wRq;X>yB%6c-<&6$0X*M%p8MpsiHW z9-XKnG}}!L4|UZ*^{S9>qHqLxt(-;Hnno=sF1Kmok4%T?Z*QSsk2q9mHOP4$NOLed-Gl1{8wF(|kfP#Q+MV z4~7q7zBI6HJ(Z6OX6?AzriPCRb*f5+ihVSeRH1oy|7LeZ(%G0K|{%i7~GM5;!r=}3Mb zsSt2*2|ol-5_=Wl&-XX>iH_5f__cs_{=Xq4U0wzOd1F)ywQLDdV?iwK*xqlO7!IKRBl8K);?+rn8VN|Cib>5$K_|K<^A3Nml>@|>Y zL7n6;5c~mxb^rp}ud1y^XNhW{0 zvLSzsDFjKlgntI`*ctwe=optA z2%?g+9^T3A0hwHk8-Zlrt5{EFNb~|L@Z<`R5frK98iFhW>g`^~)K?Mw27(U|%mD}~ zw%I(f0D=&Os~UpAode`I5fnp2bAoP?9`cv4H$H#Q)3$z7!{|&$wo&$jL{>d$2^bo}mJ%LYJW^r@HJ{ft{xh?2tdgm$iuCHh}VA z3iaNzJBn|sGKxaq^c|$bc@^w!)v95tX#WHeFrZWxnK+EMG%`UM1vM@C9s;VLKgLus z96|2k?ROARb^R%(N@_TQx91R0h5TJiX|(bN<{`lOEBrT1`;u1vGK3r6kBQ8T#LURd z%$GOTP{RWGvV{>Qsp(RL+lLX5AEIy-50o8v{3?u49)73{BRC6^07D7n&tSBut3p;W z^#cSiBB%@fSvfxYU`C~_F&zxU*{+dX|lRST>z^GN;KlvLNWvxm0x0n||u#TVy0qQjJ zcL@HM{Co2EPs5ujhrh~@6N6{f51=@Qfs((KsGO)Akvo`s~ z-u|7u8|sd%Dx^M4~}h8{L)ylwR}h03<>(6n`DjgdhwUHksD{(UVvF9+X9 zLY%S{yy0DnYwVf;dVdT)euRDFS=UfTfPl9k&~Jh0MzE~s1kY#Lq<=6XD2%n5z^N(FiOr_!kw`?nhcTSwnIz#ML|7i$y;`Vhi2>E0P z0lY-jGggeFsp(XfnaZ~&5{Fx%j|>As){gt$%XVaBeW&^97<>uG;=cQQNrhRW2QmX( zoAm%H&5|)4-I)ff->%}r9>0aMv0}6eRtSdA1PrvG;Pc5$2P|Q3E99uOq5>@kRI13j zgeeR$k`#hj016Y$4`D=5hY%$pe3=TLWkP3pau#omI5vtM_KoM)e-$4r%ooR_uqa$h z8p`-21Zd3(5g4X2Y1q((JCW5r&mTy8Uy%2{upSUkabg26HSlr+51Wy<-%44%bog`b z)NJZrI4?KR)mO3iC28Nw^1hch>cr5b*c6tU!g5o1?wwrug6aEGXTRLpzwQyoLgM=o zkvS(Z=Vay_m|caf=(ub2@~z3GN%8prEDKMBMaNmmaaMMmg_v!PPr5dYqUVIpuu_-9O^F0t~L z(+!A~znrc^to%JR+Vt=>Ts4@#eB=7(nE$9$*knfBYA`==D6HV z{EhhMnE$9$*kjSdrrv6xwgK`Vl?uIP#H|J@F63hy@)-4d^{}>G4b=8R{-aW1P;b@4 z;#)OPD~9|>rNRcQ9)tF^;X?P#=RT)pNKwRV=)%TS1GRq0e^e?QeOzM}25!PgS2YH0 n4y(QvTsVIdh8xSUF-439ryiz2HCTA#=IG~`|ENYGoh1JQPs)le literal 7688 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 P=Gg>Z#^@h2ajN+bJbh2Q delta 551 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 D|1#yB diff --git a/core/admin.py b/core/admin.py index 5466d1b..a255268 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,15 +1,19 @@ from django.contrib import admin -from .models import Category, Product, Customer, Supplier, Sale, SaleItem, Purchase +from .models import Category, Unit, Product, Customer, Supplier, Sale, SaleItem, Purchase, SystemSetting @admin.register(Category) class CategoryAdmin(admin.ModelAdmin): list_display = ('name_en', 'name_ar', 'slug') prepopulated_fields = {'slug': ('name_en',)} +@admin.register(Unit) +class UnitAdmin(admin.ModelAdmin): + list_display = ('name_en', 'name_ar', 'short_name') + @admin.register(Product) class ProductAdmin(admin.ModelAdmin): - list_display = ('name_en', 'name_ar', 'sku', 'price', 'stock_quantity', 'category') - list_filter = ('category',) + list_display = ('name_en', 'name_ar', 'sku', 'price', 'stock_quantity', 'category', 'unit') + list_filter = ('category', 'unit') search_fields = ('name_en', 'name_ar', 'sku') @admin.register(Customer) @@ -34,3 +38,7 @@ class SaleAdmin(admin.ModelAdmin): class PurchaseAdmin(admin.ModelAdmin): list_display = ('id', 'supplier', 'total_amount', 'created_at') list_filter = ('supplier', 'created_at') + +@admin.register(SystemSetting) +class SystemSettingAdmin(admin.ModelAdmin): + list_display = ('business_name', 'phone', 'email', 'vat_number') \ No newline at end of file diff --git a/core/migrations/0003_remove_systemsetting_logo_url_systemsetting_logo_and_more.py b/core/migrations/0003_remove_systemsetting_logo_url_systemsetting_logo_and_more.py new file mode 100644 index 0000000..d92d685 --- /dev/null +++ b/core/migrations/0003_remove_systemsetting_logo_url_systemsetting_logo_and_more.py @@ -0,0 +1,67 @@ +# Generated by Django 5.2.7 on 2026-02-02 07:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0002_systemsetting'), + ] + + operations = [ + migrations.RemoveField( + model_name='systemsetting', + name='logo_url', + ), + migrations.AddField( + model_name='systemsetting', + name='logo', + field=models.ImageField(blank=True, null=True, upload_to='business_logos/', verbose_name='Logo'), + ), + migrations.AddField( + model_name='systemsetting', + name='registration_number', + field=models.CharField(blank=True, max_length=50, verbose_name='Registration Number'), + ), + migrations.AddField( + model_name='systemsetting', + name='vat_number', + field=models.CharField(blank=True, max_length=50, verbose_name='VAT Number'), + ), + migrations.AlterField( + model_name='product', + name='price', + field=models.DecimalField(decimal_places=3, max_digits=12, verbose_name='Price'), + ), + migrations.AlterField( + model_name='purchase', + name='total_amount', + field=models.DecimalField(decimal_places=3, max_digits=15), + ), + migrations.AlterField( + model_name='sale', + name='discount', + field=models.DecimalField(decimal_places=3, default=0, max_digits=15), + ), + migrations.AlterField( + model_name='sale', + name='total_amount', + field=models.DecimalField(decimal_places=3, max_digits=15), + ), + migrations.AlterField( + model_name='saleitem', + name='line_total', + field=models.DecimalField(decimal_places=3, max_digits=15), + ), + migrations.AlterField( + model_name='saleitem', + name='unit_price', + field=models.DecimalField(decimal_places=3, max_digits=12), + ), + migrations.AlterField( + model_name='systemsetting', + name='currency_symbol', + field=models.CharField(default='OMR', max_length=10, verbose_name='Currency Symbol'), + ), + ] diff --git a/core/migrations/0004_unit_product_unit.py b/core/migrations/0004_unit_product_unit.py new file mode 100644 index 0000000..91f3689 --- /dev/null +++ b/core/migrations/0004_unit_product_unit.py @@ -0,0 +1,28 @@ +# Generated by Django 5.2.7 on 2026-02-02 08:00 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0003_remove_systemsetting_logo_url_systemsetting_logo_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='Unit', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name_en', models.CharField(max_length=50, verbose_name='Name (English)')), + ('name_ar', models.CharField(max_length=50, verbose_name='Name (Arabic)')), + ('short_name', models.CharField(max_length=10, verbose_name='Short Name')), + ], + ), + migrations.AddField( + model_name='product', + name='unit', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='products', to='core.unit'), + ), + ] diff --git a/core/migrations/0005_product_cost_price_product_is_active_and_more.py b/core/migrations/0005_product_cost_price_product_is_active_and_more.py new file mode 100644 index 0000000..51d89cb --- /dev/null +++ b/core/migrations/0005_product_cost_price_product_is_active_and_more.py @@ -0,0 +1,59 @@ +# Generated by Django 5.2.7 on 2026-02-02 08:19 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0004_unit_product_unit'), + ] + + operations = [ + migrations.AddField( + model_name='product', + name='cost_price', + field=models.DecimalField(decimal_places=3, default=0, max_digits=12, verbose_name='Cost Price'), + ), + migrations.AddField( + model_name='product', + name='is_active', + field=models.BooleanField(default=True, verbose_name='Active'), + ), + migrations.AddField( + model_name='product', + name='opening_stock', + field=models.PositiveIntegerField(default=0, verbose_name='Opening Stock'), + ), + migrations.AddField( + model_name='product', + name='supplier', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='products', to='core.supplier'), + ), + migrations.AddField( + model_name='product', + name='vat', + field=models.DecimalField(decimal_places=2, default=0, max_digits=5, verbose_name='VAT (%)'), + ), + migrations.AlterField( + model_name='product', + name='image', + field=models.ImageField(blank=True, null=True, upload_to='product_images/', verbose_name='Product Image'), + ), + migrations.AlterField( + model_name='product', + name='price', + field=models.DecimalField(decimal_places=3, max_digits=12, verbose_name='Sale Price'), + ), + migrations.AlterField( + model_name='product', + name='sku', + field=models.CharField(max_length=50, unique=True, verbose_name='Barcode/SKU'), + ), + migrations.AlterField( + model_name='product', + name='stock_quantity', + field=models.PositiveIntegerField(default=0, verbose_name='In Stock'), + ), + ] diff --git a/core/migrations/0006_purchase_balance_due_purchase_due_date_and_more.py b/core/migrations/0006_purchase_balance_due_purchase_due_date_and_more.py new file mode 100644 index 0000000..ec823ad --- /dev/null +++ b/core/migrations/0006_purchase_balance_due_purchase_due_date_and_more.py @@ -0,0 +1,82 @@ +# Generated by Django 5.2.7 on 2026-02-02 08:35 + +import django.db.models.deletion +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0005_product_cost_price_product_is_active_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='purchase', + name='balance_due', + field=models.DecimalField(decimal_places=3, default=0, max_digits=15, verbose_name='Balance Due'), + ), + migrations.AddField( + model_name='purchase', + name='due_date', + field=models.DateField(blank=True, null=True, verbose_name='Due Date'), + ), + migrations.AddField( + model_name='purchase', + name='invoice_number', + field=models.CharField(blank=True, max_length=50, verbose_name='Invoice Number'), + ), + migrations.AddField( + model_name='purchase', + name='notes', + field=models.TextField(blank=True, verbose_name='Notes'), + ), + migrations.AddField( + model_name='purchase', + name='paid_amount', + field=models.DecimalField(decimal_places=3, default=0, max_digits=15, verbose_name='Paid Amount'), + ), + migrations.AddField( + model_name='purchase', + name='payment_type', + field=models.CharField(choices=[('cash', 'Cash'), ('credit', 'Credit'), ('partial', 'Partial')], default='cash', max_length=20, verbose_name='Payment Type'), + ), + migrations.AddField( + model_name='purchase', + name='status', + field=models.CharField(choices=[('paid', 'Paid'), ('partial', 'Partial'), ('unpaid', 'Unpaid')], default='paid', max_length=20, verbose_name='Status'), + ), + migrations.AlterField( + model_name='purchase', + name='supplier', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='purchases', to='core.supplier'), + ), + migrations.AlterField( + model_name='purchase', + name='total_amount', + field=models.DecimalField(decimal_places=3, max_digits=15, verbose_name='Total Amount'), + ), + migrations.CreateModel( + name='PurchaseItem', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('quantity', models.PositiveIntegerField(verbose_name='Quantity')), + ('cost_price', models.DecimalField(decimal_places=3, max_digits=12, verbose_name='Cost Price')), + ('line_total', models.DecimalField(decimal_places=3, max_digits=15, verbose_name='Line Total')), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.product')), + ('purchase', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='core.purchase')), + ], + ), + migrations.CreateModel( + name='PurchasePayment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('amount', models.DecimalField(decimal_places=3, max_digits=15, verbose_name='Amount')), + ('payment_date', models.DateField(default=django.utils.timezone.now, verbose_name='Payment Date')), + ('payment_method', models.CharField(default='Cash', max_length=50, verbose_name='Payment Method')), + ('notes', models.TextField(blank=True, verbose_name='Notes')), + ('purchase', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to='core.purchase')), + ], + ), + ] diff --git a/core/migrations/0007_sale_balance_due_sale_due_date_sale_invoice_number_and_more.py b/core/migrations/0007_sale_balance_due_sale_due_date_sale_invoice_number_and_more.py new file mode 100644 index 0000000..7595004 --- /dev/null +++ b/core/migrations/0007_sale_balance_due_sale_due_date_sale_invoice_number_and_more.py @@ -0,0 +1,91 @@ +# Generated by Django 5.2.7 on 2026-02-02 09:25 + +import django.db.models.deletion +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0006_purchase_balance_due_purchase_due_date_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='sale', + name='balance_due', + field=models.DecimalField(decimal_places=3, default=0, max_digits=15, verbose_name='Balance Due'), + ), + migrations.AddField( + model_name='sale', + name='due_date', + field=models.DateField(blank=True, null=True, verbose_name='Due Date'), + ), + migrations.AddField( + model_name='sale', + name='invoice_number', + field=models.CharField(blank=True, max_length=50, verbose_name='Invoice Number'), + ), + migrations.AddField( + model_name='sale', + name='notes', + field=models.TextField(blank=True, verbose_name='Notes'), + ), + migrations.AddField( + model_name='sale', + name='paid_amount', + field=models.DecimalField(decimal_places=3, default=0, max_digits=15, verbose_name='Paid Amount'), + ), + migrations.AddField( + model_name='sale', + name='payment_type', + field=models.CharField(choices=[('cash', 'Cash'), ('credit', 'Credit'), ('partial', 'Partial')], default='cash', max_length=20, verbose_name='Payment Type'), + ), + migrations.AddField( + model_name='sale', + name='status', + field=models.CharField(choices=[('paid', 'Paid'), ('partial', 'Partial'), ('unpaid', 'Unpaid')], default='paid', max_length=20, verbose_name='Status'), + ), + migrations.AlterField( + model_name='sale', + name='customer', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sales', to='core.customer'), + ), + migrations.AlterField( + model_name='sale', + name='discount', + field=models.DecimalField(decimal_places=3, default=0, max_digits=15, verbose_name='Discount'), + ), + migrations.AlterField( + model_name='sale', + name='total_amount', + field=models.DecimalField(decimal_places=3, max_digits=15, verbose_name='Total Amount'), + ), + migrations.AlterField( + model_name='saleitem', + name='line_total', + field=models.DecimalField(decimal_places=3, max_digits=15, verbose_name='Line Total'), + ), + migrations.AlterField( + model_name='saleitem', + name='quantity', + field=models.PositiveIntegerField(verbose_name='Quantity'), + ), + migrations.AlterField( + model_name='saleitem', + name='unit_price', + field=models.DecimalField(decimal_places=3, max_digits=12, verbose_name='Unit Price'), + ), + migrations.CreateModel( + name='SalePayment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('amount', models.DecimalField(decimal_places=3, max_digits=15, verbose_name='Amount')), + ('payment_date', models.DateField(default=django.utils.timezone.now, verbose_name='Payment Date')), + ('payment_method', models.CharField(default='Cash', max_length=50, verbose_name='Payment Method')), + ('notes', models.TextField(blank=True, verbose_name='Notes')), + ('sale', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to='core.sale')), + ], + ), + ] diff --git a/core/migrations/__pycache__/0003_remove_systemsetting_logo_url_systemsetting_logo_and_more.cpython-311.pyc b/core/migrations/__pycache__/0003_remove_systemsetting_logo_url_systemsetting_logo_and_more.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..afbf14997cdb37820f883c9e8b9f59547f33df70 GIT binary patch literal 2486 zcmb_c%}*Og6yFcqtQQl)KtmG3NBBy7i4j$ss-l%pTB^1QEd(LtA}#BkiCNeWXLgqc zP8@pZF}MB)G*x`aF~=TrTuVM!bM47DBRJ*MH@i0A7!jyCyZd%#-uwOL&71e;&z>Gt zfOdOz-#k z%Q*2=$R_Jtot2nsn?Q{jFU1la5?>wnVHB}+pZL;`2k|nW7zTFj?-*k$sr+uhcZqd~OYDE>st4ecpo3KPI zxprl*w-9)yLp)F8V=u=NkKvz9uw=o~ooz4$706*S~y5GqxCMfT5RzRa<|dS;Q&&n-oP8JG$RQ z@2RWvD@}XWRiZ8q44)-S)HFz)Un=!@{h#?nR%X{QF-%*xw34M8#EZGHSu}kw=2l7p zH8ypRu#E5eV5r+};P`d!Nmio6@S`K#iG-(Hgelnc3~oUAl>#1f1J;GfGd-~U*izw^ zCL9J+Er=&X3V4|sIHs>fj}w;r;X6rfZX2P=NRZ zIr4%Sf$!4XuP)v8N)Y}WPuJY(1)qb&{ZyloQ|DP}-&ZDvt#3 zcCxZSsxnd=oUZi^HIi~#JrqD3bJ0+Q^c}7qiqwNO zH1Tq}iY8u7zF7&sT@A^0h^z{-s>o_|B{&kqF&CWp405`L#yN8Q)xg{F@W+i1=@q0` zk$y33_$=(*T!_{xXswFY&OwcEH-~G3W3|4UH8jN+n|ibK_iVWGONiDhXuXQoFM!>x z^^LZ{&_V?*RMEl(m|+Om8I1U@5Td0DTB@R@i&0lz3A}?)oY~YRy!$U0NP+Q_kbm}e zIb762v{6ADRkZQpmWcQerS^Z|4}I85w?()X(|AjFitYnkh{`Lfqc|Zy=Vp=Z7tnjJ y%*8eOHT@EXB7EK|h=weRA|$Dp4TbxiZzJ`Um}&@};-y->K6%I$$LDCHb^ir)oO6-@ literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/0004_unit_product_unit.cpython-311.pyc b/core/migrations/__pycache__/0004_unit_product_unit.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b80601f1f6680a3694873619728cd3de36133bdf GIT binary patch literal 1623 zcmZux%}?V-6dyZ|u}y#^3!846HdKqM&32X0ZF{R$qPx)2vPdnkQoK?}PG;6|@JBj! z*o6~^9(v@~{R?O-IP_MIJ@mlQNIpb!s?-xVBRK7;Gj`01!q|Q@@6DU{@tgPj>%@cx z*!Z)xYgHrw{^d+I@}tH16SnvTAbZ!9x_B=+Qyd^onxaynpNI$nrKNi>yAf>fj(j_-?1i+z_`g=<<9H)u`% zPiqPluWOmoVd<)Uc1r50nKx8Mw-P(aJ#ZeCE&b_r6)BbYv#pP&B}66|SJOVwzY$4 zVGEPC=VRy^4yI$ALhPPCWSQ+ut}<@+-lA*Tmfx)>1!KH|Hg?UROQx90=zu|XCQ{u6 zVzjMJooas9BSB*Hlw-Q1t#QFD*Wx*IKU98UVH^2$tWP{N=mb<6@FbKG>+h%>ng$ov z^EBVK4R@0r9oRNiJr}Za#R1EJV4J6yW*`P$J;w^07jq&Zw|SiT#MNrONEMifA*32) z(4m2i*}4GXj|0QzVQ~)OK6VjyI~Mi{?;kq;I05o0w@Y{})54dQxxgS^;z>~Tan~T} zQdvMKU1>`!e#>;fWthvTXSk+EMbxHB5;|);s;n)p!_|$I6{>lCJnF^Y!8vmP-Yhq} zo`ai%_P`AWP5cUXm^;#Z>5)ypZ**{z!_$1*!%bF0pFs{696^{cjs7;_)teCBp8h0j z=iSREmVvq^#5r+V-d%|`pw@oYBISc&sq#;0Cam1w+lngpkEG+dP=}#Q!is-V@uP|# z7K35w-LU%L^-@%Qa3qGyt+1+}RQ0H;hsEbt_Ks^|wRKW$Mb%bV)Q6?_!&>w8R#a;q zJv?3sYps)7E2{CWSj|sQ#(726&Hy79oWy0IR(VYC4J)%OtYP`?-bYdS?r(E{tpD}w z?^d+95iR}@Et)X^FNFn>O-tf;e7g0$c$UzMgvMhMx68-{C-KZwS>FAIKjf1Ppt?-& zq*v8I?MC`jY-IOEK4xlEz1&Y~BPoXz;dyQ)F~u9`jq*iWV2{Y>jAft$zs*RT69j>w kE_@h*+4LQPd*MxQEYApX46c(sEh^5P@r{ccc9R(X17;z=RsaA1 literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/0005_product_cost_price_product_is_active_and_more.cpython-311.pyc b/core/migrations/__pycache__/0005_product_cost_price_product_is_active_and_more.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..30e4dc4e857ce7906ac0bd6039f34947d3b4eb36 GIT binary patch literal 2495 zcmb7G%}*Og6rWw&i@n%<43t2C)ZrtFRRyCcy|hs)l0XC@6a-kRrDnVKOw7W1*Vzve zeB#hUkGb_fpj7z4vFeFK4>_)-J)pVv#LWm!oH{e>HH08YX1%lXcJ}?|H^2AZ$bY6% zGNA43mu>BN9Dx6r5w=)!aPo`}{s0IdK?iw(K1E$DNAi(!G#?d!2%ZBJ`3;~bj;x11 zU?&2=9=(H>k0D_hWMW6mT*!z-E^8%Abu`nkiBvWb*6m{RTmI;38YlmVY;DZf&5KAV z3HgW!aP)j7BqH`kT5k&hP!z?GgxYHHohW5N@1WHZAG7)dN9H(GhoGT$l&qz8D1$wE z2kl0T4vvuszo$$(zZj#7WAMl@9Ll2|x34Ed%yp?XO0`BE;RtmjSnJuLk=ScR<3`rK ze8qDGzoNYR(5=r>u(P$Yi26BB7(LD$4WQe#!Ow`qMVz5ae4;d-!?imf$M4ErT}1hs zqjb0UrKys4Ya?HXGRjddMs)0^h?Ww0h{kEg-y?~aVW!ixj}}b}lfKEx$w!K7XpT~` zOym|FZjiR7MdYGsvpKDZk5~;^Ed7pH{HyX)I+b2zQ<*5~K)9%tRb8p*Y7yI{4dK_S zt2?y5)OQNfN}6MnOoP4PiS zv{+(UETSZ3R=&G?n zmvwcWBuqo0Ot6DV(!#pxU=*+t9g{>hRp*HBn`LpeEjP82zCWHJF}mLpu6BmXansIL zQ;UI==~*_PiCP1U_ie^I;Z}50MT%nv`-pL6tMam{YS^||+(~>2p&1q%iydMl`78)Ge?^i{OjF0I5m>#8 zrmeBq%^D6aVJmnm&(Hv9CF2F&A`)6xjgm=3R3LG#J3W3$e0h3BSy-K)C-Ri;wEVb= z0Y>8Kni|5<3}_zPe}NN937p~VnpwtKx8NF%o5k;N(WN?P-W#K&LOuqxJ7wJ^$Dz=17ajC_aIOyL zd^mT#Px=r(V2eH2R}V}N=ISu#!`$^ggNJaG`Hb$4?XMi<{{7jXUiGJ6`O~Ea0B?mU zkv?X`Rrct~iO0M@<^A=zeIb9oan*ZO^59w>uK9573dcK4v0<)Q|3yOHbk4($2i-b! zedylk+0y!+S`u*FCsYhf6+O zy6m$oOh9RSj$PLGVFqO+G7yx)1S(7fe?BI{dazi{lSFHt*CDUIAO*i;C>-iB9i7D= z2gUq@^&>R}dfNrsH6nr_(6kipdLSMA4#9ozs$WBTBuEYLY4=|CyT*>$#OW2YoWuX{ CIEBXm literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/0006_purchase_balance_due_purchase_due_date_and_more.cpython-311.pyc b/core/migrations/__pycache__/0006_purchase_balance_due_purchase_due_date_and_more.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f09ea954f3dd70fbfe25f03941bdbf3a1859683b GIT binary patch literal 4370 zcmbtXO?1=P6_ylRu^lHQA`tQ)CqOYXI1oBapwnqXOhO>gBxKst3>}T^_u?q%$C2a= z@oZRh(dF!h<(w|)%&8YFvtYrJA~>=xhak^d1mWNC1lNj;#0Nq;y2HBn(-KXXdcn~=-L_mRn>NCRn{UYS>P(~a zrVQGAFJQj^iiE_1xY8mCuv4g|fF#65fU`+7&%+l|F_ zi16TA?+jaL6pdBR9z*xN`<*+)r;N^5esc`Zb~X3CfYKaAX^yZcyNEuje7s+qRS8|< zk|&#znacPx5h4Jd-~yS;hjhAvCJyQZa#s(@$>_I7$bG`)nw-9u(e6+ebglB~{`iDl zrC*IUiLO^}>}PgWD#(@J?cW3NBkQ~De)a+E{{9F?K0ktyKXAEbRscp$g*LCBZnk>D z7W!jlYQK)a!%dE{n-iwd36!neI%pp{#;O0mSe-c@tA)0eGiVkv_H7!!7iMZm+mXEO zU^;R6^5sv|qGO{{-c$3o>p@4)H-ov6Yze0$v;*P1Zfb^FG_*W+X)D63TFLO>^`SjhkzUX}mnJu{ zle1l{TAGQ|EilJkC%i`^Y>Y-%b!;FSgAG-rd3d=T1N@OJG(kR^19cvOXvr`fmLF(` zZf)Ak9;;;Lu(LD4RO^@v21oAm!E}qmq9D-3z*fOq3l>K$+XGdj_gR&exKuFHR@8K) zYNlPXymH$DbVjClhbD{KmWeG-^|p$j5+_=7*JxxKO6bXX2P561@-(l1l4xsDb39!$ zXzN1IaE3se9d;PY$%O!IWSOBwXls6rS>rk^H9Ebl;AYCMr+Fne*pKN(BTOl06eQTD zC5sQrU+~(w0K-&%z}L{2TPhX}9Xm80I@T?>v*^f#GwF!KP6v(KmI@BR9;QhL8$db= zoFjQQO?tKm{%w$wTxPunE5L9&)0G8Apm1`b5kqqxHmRhe87j?X!NR0g@@y4IV&Jq5 zg3;8RE%g!JqHz~Hz>{9a0Y+@2q*qesl;w1@38z=PE12+k1VjzrU^s0 zu*$V9C+49&!gU2l1jnG9?Hfd@yKONbfqGN3|6AI*8^lxJ>M=*~l4&UCJF{jNIdcpv{5ch&5KZ_tU*J zrgD#|Dov$bJ!;sO=g4B%!g>4 zDcWdGuW3%Bxr`rsK{GLBc(^f~n1RUB3)WrmsEpP%t6)U=uh}LZFXiAQm&Wm9oQEx(@o#MBkz0gJHO^vTeD@P~ z91`X=HFUkYCjssbhRLa!GBd?3hkZ`-g1vp*zW|L-n!ma%#An5Ni_|SYnXe~=_`p9B zeyqRNld5$M)s%DJo%fY<&lg{cuX02+h+6$cjf*OXDVaP-IS z=h>HKqAXRFC0|*3_o~$Hs_jv7_e-KYtSS$E<>9-Ro!PT&mMGb(lJ%AByVv|`%Z9_v z$VZPFcj?=@Ak0a3C8$=UHCCO}rPsWC%gcA`()&Kh9EZ$v$h^cL^U@KJL(Fl5+;PDF z7~_BJ$L{UZFM9p!bN=-u|N56?y#(5QE8gLVcRAu+jtJTx_UIl&u)7%w6J$9@lzdgm z`%3H>2a$)uHWO5POsM0PX;HS{@EX# ze7Q~r7psGd{@^0%T&i^qlHrM`oBr^`%M@8yBAQ8tt?IDl4_l z*Q``;Z|&}=M-%J_K=GCp_0vK;w0!g!miLnW%+qUre`Z^Ib(8!Jlm69e|Ek}=N;(U` z9Z6mJ;k2K+vVH!=$Nr5!`8OVrT#=+Us;Lb>wLvez-q7R;U@eHWje_xmnB?|wN;`sS;B^M2nv>Ad&LX1ieBmUx?+J;%+S<7Ursvmt1X zGTRliq9Q-J!+wq$W}0*>?F}+)2IVrrU-L|uW}PJvqo*44JY8m**)rZ-#?wDf`auT! zih=VTzJ}J%vCo2c$mb4%F8ed%K8B*+B8nnpH8Dkmk>IWg10=j_!Ub~FU6=2OqFfh_ TF276ooo8S3$+y2C!ZrIJtPgZG literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/0007_sale_balance_due_sale_due_date_sale_invoice_number_and_more.cpython-311.pyc b/core/migrations/__pycache__/0007_sale_balance_due_sale_due_date_sale_invoice_number_and_more.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f1d228d0e463efc05f84b8c9e10c946e09efa4c0 GIT binary patch literal 4242 zcmbVPOHdoh86FMN=z+j!jEw=?Ag~>aT?2k@;%qhn1TJDAEW)l@;_P^&Zp6r=88Hvm zD3yv2IppZu`nZ(?*{#)qLk=8r@F52uM^$rB^(j?3ag)nF?J0lvXaoYnBO}e<-T(Ce z_5arB?;Ra64lcX+N*OrEasQzU{|YrGALd~4Er&Q1P`Q-=8+kVJYFjb55-f&RLIIBF z9&xDcFB}TuU_tOfw%R!EN4ULfML>ZJmkRCDJUzh z;Y9rXs2x7>N=K{Ii71^6CAtsg9O|kh&WIv16c#0l1e%<$bZ_xs58U2$q9zv@#zDVa zJhNYVS}n?R=+fT;s2BC2{>tSu`uNHrtgC=E09b>k!fN-ehqxyhR=ml9c;Svhr@!9I z#scKHcNMz)85%;vm1|p&M?W@m=}3?6Kk@pJ9=&lYkM76$*&!SO-K?Zekt2I3Bj}SO zP(D2~$}NVHX!>QeGPV@}ufXkHCwOH9jUUqEHo9{vJw*QubQj%YDCe6TT7CD?gUV;E z8Mex!pC39;K)*P69_)L__BHDna--WvFI;HOF8VX{%gX1i6$Ti;ViYNOa!svCwdXIo-hI z-uU?V1F2*ixi#6uQdU-FEr%s!<2@!_Albr_tRbmLrGlocV$!~E_b%=125m&Uw6eURTz#h#mX0fq*hpN^& z>oErx7&Fyo(8zn5s94KeYo2gO(=AXnv_OXx&vtoF+Do#6B)O>DnpJLJgvH*HQs#)%!c`@p9Qz6f8S`s9xYZ62RTbRTRtb*5(7aiWxAt;KOqaGoVv_Fem zx&=XRcq+b3R}EKz+dHTMG+R6?BJ_O4!bK8O6%9*Fwj7&-`5==dk#$?vEXCR+kuMvw zRKOFC*_vWWB?H{EON*%-djgEXMam%U89;9MlJE+eA^dC_{27;ROP3%d)DqGT(nZ_;?xUCqxljYdSIpsBWEpT;~}RTyY?v zk%p96jZ@@|ZUcdoq|K5DtSHQdR4<80EFO|XViHs;vQ-T8xFmt&R9ZoF zg>VUL2x~b7GKrQgiA*3gLo3UmZ$>1Z_KKCUNR-;tm`<(9Mq|2+Us~QYHlbR$!H&&9 z@+f)jF}P4b1zF4MghyFO4w^Rbaz`SW>1Ao*$=n?2Uery+Qa13cX5qY#&<>RW2hP*d zC-F%oKLIiF4hq?pqM9UPDMeh?HB8zx{TmXVn#fE|q^C(tFX6@;F1^k>Gr2QGcp{L}?5Ivc3B@!i6!xq6I?UjA#$jr7-guGA7&YvT3SH(c@h_Lpx1 z@3M}hI%2UZ7G1Ge4+o>M_Z*OqlpGN3uZcrcaOnH)?eyESBQ90NC0ATJUMjgKwKL>A z{=FkUtBTKD@!9cWm-dN0a>R61OuJ(Gc$xp(vH|}v=aUzWul>h5$IbGOc^FpsH9DME z`1g!{Wb}AldQXDPGRPc*%<&X5#~%TCl?Lt#3mo`=nEHSC`)k|FZ=e3_D|h;dJN>mg zop(0=1o}=Mo&M1`58VFI9r@j)^9StouU7k4-TqalD_=_tILX`J zU38PTcW(UiQ}^NT+=m$_TXK@?)#SRHTz9&RS|aHT-~H}^JA8L%=AGd@FFC{O)!}t_ z*n4`(=^J@-!|fZ{?tc5o>6@$e&AEMZPS<=b(c|=vyt(N1j%+{u$5-zCY4`qud;h6} zb;tTMguics#vLf+5>VhICi!|txVy6+iqWzL@{yAIG(R5@g;&3&A4BrnNGeQvy;eGk zvZLN#<59n#8cVQ^TxdLJ4RW;EAEQk%*5_Gw_uA9f6r7##9A-a7KKFhszAz9h>1)3E z5=gx*5C}la3M3tF(EHZ7%Z~r8aW|c#zI7oJ2ncoVX!<(lc3peV9)A2ETbO451=F`p Av;Y7A literal 0 HcmV?d00001 diff --git a/core/models.py b/core/models.py index 5568852..4569aea 100644 --- a/core/models.py +++ b/core/models.py @@ -1,5 +1,6 @@ from django.db import models from django.utils.translation import gettext_lazy as _ +from django.utils import timezone class Category(models.Model): name_en = models.CharField(_("Name (English)"), max_length=100) @@ -12,15 +13,29 @@ class Category(models.Model): def __str__(self): return f"{self.name_en} / {self.name_ar}" +class Unit(models.Model): + name_en = models.CharField(_("Name (English)"), max_length=50) + name_ar = models.CharField(_("Name (Arabic)"), max_length=50) + short_name = models.CharField(_("Short Name"), max_length=10) + + def __str__(self): + return f"{self.name_en} / {self.name_ar}" + class Product(models.Model): category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name="products") + unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True, related_name="products") + supplier = models.ForeignKey('Supplier', on_delete=models.SET_NULL, null=True, blank=True, related_name="products") name_en = models.CharField(_("Name (English)"), max_length=200) name_ar = models.CharField(_("Name (Arabic)"), max_length=200) - sku = models.CharField(_("SKU"), max_length=50, unique=True) + sku = models.CharField(_("Barcode/SKU"), max_length=50, unique=True) description = models.TextField(_("Description"), blank=True) - price = models.DecimalField(_("Price"), max_digits=10, decimal_places=2) - stock_quantity = models.PositiveIntegerField(_("Stock Quantity"), default=0) - image = models.URLField(_("Product Image"), blank=True, null=True) + cost_price = models.DecimalField(_("Cost Price"), max_digits=12, decimal_places=3, default=0) + price = models.DecimalField(_("Sale Price"), max_digits=12, decimal_places=3) + vat = models.DecimalField(_("VAT (%)"), max_digits=5, decimal_places=2, default=0) + opening_stock = models.PositiveIntegerField(_("Opening Stock"), default=0) + stock_quantity = models.PositiveIntegerField(_("In Stock"), default=0) + image = models.ImageField(_("Product Image"), upload_to="product_images/", blank=True, null=True) + is_active = models.BooleanField(_("Active"), default=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): @@ -44,34 +59,132 @@ class Supplier(models.Model): return self.name class Sale(models.Model): - customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, null=True, blank=True) - total_amount = models.DecimalField(max_digits=12, decimal_places=2) - discount = models.DecimalField(max_digits=12, decimal_places=2, default=0) + PAYMENT_TYPE_CHOICES = [ + ('cash', _('Cash')), + ('credit', _('Credit')), + ('partial', _('Partial')), + ] + STATUS_CHOICES = [ + ('paid', _('Paid')), + ('partial', _('Partial')), + ('unpaid', _('Unpaid')), + ] + + customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, null=True, blank=True, related_name="sales") + invoice_number = models.CharField(_("Invoice Number"), max_length=50, blank=True) + total_amount = models.DecimalField(_("Total Amount"), max_digits=15, decimal_places=3) + paid_amount = models.DecimalField(_("Paid Amount"), max_digits=15, decimal_places=3, default=0) + balance_due = models.DecimalField(_("Balance Due"), max_digits=15, decimal_places=3, default=0) + discount = models.DecimalField(_("Discount"), max_digits=15, decimal_places=3, default=0) + payment_type = models.CharField(_("Payment Type"), max_length=20, choices=PAYMENT_TYPE_CHOICES, default='cash') + status = models.CharField(_("Status"), max_length=20, choices=STATUS_CHOICES, default='paid') + due_date = models.DateField(_("Due Date"), null=True, blank=True) + notes = models.TextField(_("Notes"), blank=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): - return f"Sale #{self.id} - {self.total_amount}" + return f"Sale #{self.id} - {self.customer.name if self.customer else 'Guest'}" + + def update_balance(self): + payments_total = self.payments.aggregate(total=models.Sum('amount'))['total'] or 0 + self.paid_amount = payments_total + self.balance_due = self.total_amount - self.paid_amount + if self.balance_due <= 0: + self.status = 'paid' + elif self.paid_amount > 0: + self.status = 'partial' + else: + self.status = 'unpaid' + self.save() class SaleItem(models.Model): sale = models.ForeignKey(Sale, on_delete=models.CASCADE, related_name="items") product = models.ForeignKey(Product, on_delete=models.CASCADE) - quantity = models.PositiveIntegerField() - unit_price = models.DecimalField(max_digits=10, decimal_places=2) - line_total = models.DecimalField(max_digits=12, decimal_places=2) + quantity = models.PositiveIntegerField(_("Quantity")) + unit_price = models.DecimalField(_("Unit Price"), max_digits=12, decimal_places=3) + line_total = models.DecimalField(_("Line Total"), max_digits=15, decimal_places=3) + + def __str__(self): + return f"{self.product.name_en} x {self.quantity}" + +class SalePayment(models.Model): + sale = models.ForeignKey(Sale, on_delete=models.CASCADE, related_name="payments") + amount = models.DecimalField(_("Amount"), max_digits=15, decimal_places=3) + payment_date = models.DateField(_("Payment Date"), default=timezone.now) + payment_method = models.CharField(_("Payment Method"), max_length=50, default="Cash") + notes = models.TextField(_("Notes"), blank=True) + + def __str__(self): + return f"Payment of {self.amount} for Sale #{self.sale.id}" class Purchase(models.Model): - supplier = models.ForeignKey(Supplier, on_delete=models.SET_NULL, null=True) - total_amount = models.DecimalField(max_digits=12, decimal_places=2) + PAYMENT_TYPE_CHOICES = [ + ('cash', _('Cash')), + ('credit', _('Credit')), + ('partial', _('Partial')), + ] + STATUS_CHOICES = [ + ('paid', _('Paid')), + ('partial', _('Partial')), + ('unpaid', _('Unpaid')), + ] + + supplier = models.ForeignKey(Supplier, on_delete=models.SET_NULL, null=True, related_name="purchases") + invoice_number = models.CharField(_("Invoice Number"), max_length=50, blank=True) + total_amount = models.DecimalField(_("Total Amount"), max_digits=15, decimal_places=3) + paid_amount = models.DecimalField(_("Paid Amount"), max_digits=15, decimal_places=3, default=0) + balance_due = models.DecimalField(_("Balance Due"), max_digits=15, decimal_places=3, default=0) + payment_type = models.CharField(_("Payment Type"), max_length=20, choices=PAYMENT_TYPE_CHOICES, default='cash') + status = models.CharField(_("Status"), max_length=20, choices=STATUS_CHOICES, default='paid') + due_date = models.DateField(_("Due Date"), null=True, blank=True) + notes = models.TextField(_("Notes"), blank=True) created_at = models.DateTimeField(auto_now_add=True) + def __str__(self): + return f"Purchase #{self.id} - {self.supplier.name if self.supplier else 'N/A'}" + + def update_balance(self): + payments_total = self.payments.aggregate(total=models.Sum('amount'))['total'] or 0 + self.paid_amount = payments_total + self.balance_due = self.total_amount - self.paid_amount + if self.balance_due <= 0: + self.status = 'paid' + elif self.paid_amount > 0: + self.status = 'partial' + else: + self.status = 'unpaid' + self.save() + +class PurchaseItem(models.Model): + purchase = models.ForeignKey(Purchase, on_delete=models.CASCADE, related_name="items") + product = models.ForeignKey(Product, on_delete=models.CASCADE) + quantity = models.PositiveIntegerField(_("Quantity")) + cost_price = models.DecimalField(_("Cost Price"), max_digits=12, decimal_places=3) + line_total = models.DecimalField(_("Line Total"), max_digits=15, decimal_places=3) + + def __str__(self): + return f"{self.product.name_en} x {self.quantity}" + +class PurchasePayment(models.Model): + purchase = models.ForeignKey(Purchase, on_delete=models.CASCADE, related_name="payments") + amount = models.DecimalField(_("Amount"), max_digits=15, decimal_places=3) + payment_date = models.DateField(_("Payment Date"), default=timezone.now) + payment_method = models.CharField(_("Payment Method"), max_length=50, default="Cash") + notes = models.TextField(_("Notes"), blank=True) + + def __str__(self): + return f"Payment of {self.amount} for Purchase #{self.purchase.id}" + class SystemSetting(models.Model): business_name = models.CharField(_("Business Name"), max_length=200, default="Meezan Accounting") address = models.TextField(_("Address"), blank=True) phone = models.CharField(_("Phone"), max_length=20, blank=True) email = models.EmailField(_("Email"), blank=True) - currency_symbol = models.CharField(_("Currency Symbol"), max_length=10, default="$") + currency_symbol = models.CharField(_("Currency Symbol"), max_length=10, default="OMR") tax_rate = models.DecimalField(_("Tax Rate (%)"), max_digits=5, decimal_places=2, default=0) - logo_url = models.URLField(_("Logo URL"), blank=True, null=True) + logo = models.ImageField(_("Logo"), upload_to="business_logos/", blank=True, null=True) + vat_number = models.CharField(_("VAT Number"), max_length=50, blank=True) + registration_number = models.CharField(_("Registration Number"), max_length=50, blank=True) def __str__(self): return self.business_name diff --git a/core/templates/base.html b/core/templates/base.html index 25a3a05..0602d98 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -32,8 +32,8 @@