From f659ed99ad588e847702f1f2b87c79aad957f502 Mon Sep 17 00:00:00 2001 From: Suherdy Yacob Date: Fri, 20 Mar 2026 15:18:52 +0700 Subject: [PATCH] first commit --- __init__.py | 1 + __manifest__.py | 18 ++++++++ __pycache__/__init__.cpython-312.pyc | Bin 0 -> 203 bytes models/__init__.py | 3 ++ models/__pycache__/__init__.cpython-312.pyc | Bin 0 -> 292 bytes models/__pycache__/pos_config.cpython-312.pyc | Bin 0 -> 825 bytes .../__pycache__/pos_session.cpython-312.pyc | Bin 0 -> 2841 bytes .../res_config_settings.cpython-312.pyc | Bin 0 -> 670 bytes models/pos_config.py | 10 ++++ models/pos_session.py | 43 ++++++++++++++++++ models/res_config_settings.py | 9 ++++ views/res_config_settings_views.xml | 15 ++++++ 12 files changed, 99 insertions(+) create mode 100644 __init__.py create mode 100644 __manifest__.py create mode 100644 __pycache__/__init__.cpython-312.pyc create mode 100644 models/__init__.py create mode 100644 models/__pycache__/__init__.cpython-312.pyc create mode 100644 models/__pycache__/pos_config.cpython-312.pyc create mode 100644 models/__pycache__/pos_session.cpython-312.pyc create mode 100644 models/__pycache__/res_config_settings.cpython-312.pyc create mode 100644 models/pos_config.py create mode 100644 models/pos_session.py create mode 100644 models/res_config_settings.py create mode 100644 views/res_config_settings_views.xml diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/__manifest__.py b/__manifest__.py new file mode 100644 index 0000000..836467a --- /dev/null +++ b/__manifest__.py @@ -0,0 +1,18 @@ +{ + 'name': 'POS Shift Close', + 'version': '1.0', + 'category': 'Sales/Point of Sale', + 'summary': 'Bypass POS session close draft order validation for morning shift and transfer orders.', + 'description': """ +This module allows cashiers on the morning shift to close the POS session even if there are unpaid/draft orders. +Those pending orders will automatically be moved to the next session when it is opened. +The afternoon/evening shift will maintain the standard Odoo behavior (blocking closure if pending orders exist). + """, + 'depends': ['point_of_sale'], + 'data': [ + 'views/res_config_settings_views.xml', + ], + 'installable': True, + 'application': False, + 'license': 'LGPL-3', +} diff --git a/__pycache__/__init__.cpython-312.pyc b/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..397fa72480859b61e95374cd3363d0c45582b1f8 GIT binary patch literal 203 zcmX@j%ge<81UZa*Gev>)V-N=hn4pZ$0zk%eh7^Vr#vF!R#wbQchDs()=9i2>VNJ$c zY`OU7EiTE=O-xD2&nwn1$S;mB&d5wFiBHbSFHY5ukI&4@EQycTE2#X%VFR?IG$+-r Zhy!Ra$lhWQ;{!7zBjY^=ks>xA2LNU;Gynhq literal 0 HcmV?d00001 diff --git a/models/__init__.py b/models/__init__.py new file mode 100644 index 0000000..55380fe --- /dev/null +++ b/models/__init__.py @@ -0,0 +1,3 @@ +from . import pos_config +from . import pos_session +from . import res_config_settings diff --git a/models/__pycache__/__init__.cpython-312.pyc b/models/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8be57cc1cd351f95c10a38165ee820f3f6cd757f GIT binary patch literal 292 zcmXw#F-`+95JhL!8!1W@8X73+*aj;l65G=Q z2xD1Ieo@*h+|D+{rlGL%YjN}J*fDzwVlSgRUrL&i4{Ls^$}L1CuVa~IBJu%yQ8+y7 zmU^doXkE0Uei>6sp&{!uS)%2=ZU#=&zgbIx_3^LPuaapxVyAm)>{uJ)gHOiXZN|g& eLBgSPk1;PGkIWY$e+m(IaJ7T$Z@7t5iT(i}1Wthf literal 0 HcmV?d00001 diff --git a/models/__pycache__/pos_config.cpython-312.pyc b/models/__pycache__/pos_config.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..82441fc2ad2cdea7af7f9cc70770bf610786aa7e GIT binary patch literal 825 zcmYjQJ8#rL5MDp;I9*6U5Frq-1rk__&Y}dN2!VJMBpeE&up(K;J3eo)caL40$f+pu z1E4xmQ6u;X@C&HuPHsS=qKlN!q+-{;OW0!l&FsuKPk&!n=pl8u@r~{o2>sT={5Ugk zu&TfW5k!m(P3u5BaPJ*%T2+tYLzIXVtDOgrcrH1d^!CirP*g z8?UR5bi0nDOZ9!Ed*qQMWT1 zj4Pp+skw9-@|wDK(RA_B+sD5ydmmnW+}zuoE?%E5o!d9j=_l4<$G+nJurG=;bGLQR zscjv-sYBRGUps4h-&*P=ZP-|!Rp_XDr|rk8IIl$Y7a(~-UHg_{7(da~L%U0(4{Vm5x!xXoyXVXv zrI#b=Lle&j6L~O|Cm=BbArX8q@?zqn#zcG8(7H-OqA$MH6eHouZ)R_MEtJH`W#*f2 zX1@96`@Y|A?}whA1cG)!`o_8)Md%Mc37gs$mI@%uAdIkNql#3JBrZqnNJTElLY8f% zpny)X)rwkBB@{u25mu)WjuNSy3=MY1>vA9$Vv}+<_7|UIB|lnmF|ip)UB?-rhbVRF z7|i0>@Q7zsh}p4#kkXzky#m4q;2=Q-38O*;O9i=!iV}`Y$H6U`s8}JtK;@O|?XB0_W4Zmh z_EZd5nnu^#XU<6tiGBq(!Y8cQScWV-+~`HGE7X-mzQS2Bf~7TLT<%@Hi_gmI9@U+d zSOl^dS@tgCZ3uUjH-=JY^x~mh?hB>P7+NW3I=WD5bncKFa=Aa$cSg_-bY`K1<$55rp5}b6M zqBUNl1Zx$SI+io8vyxTxbmCxLm~#VjydX`nIk#+TRb|2p5zp3=2A!x&7$0s}I{hp{ z@YcwfuZrgYs8o2RN4OxK#&~UN58lm}+zQFFT8U6RnLj$|m0ZUJSms@b#f$s$W{r7n z#lYBgSRUMKJ8s%8Bl!TofjWRut~&W3zTY+~r?4?xf29MqoGxOiTSeV*b>WmAIFj3M zyQX2YVGx3IypS2M+$r>(`cwD!-Lof7wnk4*p(XxOUq1fcTjN|pXzajy*(dz<x{F7DBdaB10vO+s}KI4Fmpy>pF?+l3N0qkrh)l%u9eQsrC*p&?`y&DwUcvc zy_x*AJN@awi(?nY<_AW89vJz(uYaa;zH;f{mC?(iSB_jhGC%lIYw)F?2j94*&h-s9 z5C5LtI@37c_)Kh=OFuuK-rGv=y_KFzk2FUYGTSb0zOeb~vp4eB^0yAQh7PncBh9z( zrZUTSwBVZ5$ldf)f2KBmIMGVIFqhgr8{bV+;Huwa8mx56J*%HF>>9+^aXrG!m2}H< zBdGc|2%M@t7=tFj59CR+Btb`I>8d>uX0RmTGinQ=4X~ku(P-pH5X;9V0By$ zX^|5c-P};hJX;Sn90j{;(7PptrzO;o0miZV&1E(?Q6i3JXbzds+7+(>*SJ;U5WGv9 zAX>F%+k$jf@EFsI)U60@m69`-TVvX?MFC|TS_500?Z758faU`1?9!MkP6IO%>bSsG zg1O(9a#aS9@98`MoL0eKkGi%F{~!#S7Pc5Ms9B<%HT?)&tWHo1h%idG zfrjqn9k3+;oD$~uf*s5a1>8iRft5!<_dF<9gYI!I@J}9lA36Zty##^^A8+;MZ}!}( z&GjB?%8OCdzvbel3!5%I*UIGPGcUI?FE8|LzjW;C;Emk1-2Bk%t)bUvllvEv`Gw^6 z#hCJHVhJhz-Ob*=lPKB0knEpNYOSPpY1d8V>)4mE*=>7&N$$IwTHj0v{A9cQxUM^p zNL}|6x(<9@v$>qq_4jLr-Cl|7mIM56d4f)S*>XHzW*(*dp?*)BeN1B8x}KGSnDEcX zfq;WJXejSEIk6qk-JeDase!4{drC^(v-tEA>V}(a5y9~7o`3o9H}8ap;#@ZFEBtdq zIgtGn7jxvSNva((_xr${}|8kqAW?$ g9hAOn+a literal 0 HcmV?d00001 diff --git a/models/__pycache__/res_config_settings.cpython-312.pyc b/models/__pycache__/res_config_settings.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..65aa190ff0422b77c1528cead15c51136782a85f GIT binary patch literal 670 zcmZuuJ#Q2-5FPJVP7xtFfubmif6xGABsSR~k+?hy?+ahLUSV<+^VXVh&WH1#38&=VMCwfUyUKmPjPtk%%!Rv!` z$@VZ7MMW{T%NRR5Q||FCjNeWRzWCF_OpK{wM)_{oc%RF{*iHZN|DQXDx%YCNXJc8> zOivw~)Y;40jHM`D&oW7*eE1|Qr`pJ>AWn|X{C%zLl1ojqwz-)z-TCWG4=1(q!)k)p zq_94?eD+ZJln-)*j9J>sN93Ai0dT)mle5NMjp$0gWBy2WD@-QH9Wq z3iHB`w7q@RGa6doq3hn$wm-Y-u1k^o^rj2HLWIyaxOWvLDEW|l-56YM41R-48tz{* CNV3xa literal 0 HcmV?d00001 diff --git a/models/pos_config.py b/models/pos_config.py new file mode 100644 index 0000000..f1c49b1 --- /dev/null +++ b/models/pos_config.py @@ -0,0 +1,10 @@ +from odoo import fields, models + +class PosConfig(models.Model): + _inherit = 'pos.config' + + morning_shift_end_time = fields.Float( + string='Morning Shift End Time', + default=15.0, + help="Time (in hours) after which closing a POS session will run the standard checks for draft orders, preventing closure. Before this time, the check is bypassed so the morning shift can close." + ) diff --git a/models/pos_session.py b/models/pos_session.py new file mode 100644 index 0000000..872b937 --- /dev/null +++ b/models/pos_session.py @@ -0,0 +1,43 @@ +from odoo import models +from odoo.exceptions import UserError +import pytz +from datetime import datetime + +class PosSession(models.Model): + _inherit = 'pos.session' + + def _check_if_no_draft_orders(self): + """ + Bypass standard Odoo draft order check if the current time in the user's timezone + is before the configured morning_shift_end_time on the pos.config. + """ + draft_orders = self.get_session_orders().filtered(lambda order: order.state == 'draft') + if draft_orders and self.config_id.morning_shift_end_time: + user_tz = pytz.timezone(self.env.user.tz or 'UTC') + local_dt = datetime.now(pytz.utc).astimezone(user_tz) + local_hour = local_dt.hour + (local_dt.minute / 60.0) + + if local_hour < self.config_id.morning_shift_end_time: + # It's the morning shift, we bypass the check + return True + + return super()._check_if_no_draft_orders() + + def _set_opening_control_data(self, cashbox_value: int, notes: str): + """ + When a new session is opened, we pull the draft orders from the previous + closed sessions of this same config, so the afternoon shift can handle them. + """ + res = super()._set_opening_control_data(cashbox_value, notes) + + # Find draft orders belonging to closed sessions of the same POS + pending_orders = self.env['pos.order'].search([ + ('session_id.config_id', '=', self.config_id.id), + ('session_id.state', '=', 'closed'), + ('state', '=', 'draft') + ]) + + if pending_orders: + pending_orders.write({'session_id': self.id}) + + return res diff --git a/models/res_config_settings.py b/models/res_config_settings.py new file mode 100644 index 0000000..7adc510 --- /dev/null +++ b/models/res_config_settings.py @@ -0,0 +1,9 @@ +from odoo import fields, models + +class ResConfigSettings(models.TransientModel): + _inherit = 'res.config.settings' + + pos_morning_shift_end_time = fields.Float( + related='pos_config_id.morning_shift_end_time', + readonly=False, + ) diff --git a/views/res_config_settings_views.xml b/views/res_config_settings_views.xml new file mode 100644 index 0000000..3b284ea --- /dev/null +++ b/views/res_config_settings_views.xml @@ -0,0 +1,15 @@ + + + + res.config.settings.view.form.inherit.pos_shift_close + res.config.settings + + + + + + + + + +