lodgment-calendar 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lodgment_calendar-0.1.0.dist-info/METADATA +177 -0
- lodgment_calendar-0.1.0.dist-info/RECORD +14 -0
- lodgment_calendar-0.1.0.dist-info/WHEEL +5 -0
- lodgment_calendar-0.1.0.dist-info/licenses/LICENSE +105 -0
- lodgment_calendar-0.1.0.dist-info/licenses/NOTICE +20 -0
- lodgment_calendar-0.1.0.dist-info/top_level.txt +1 -0
- lodgmentcalendar/__init__.py +11 -0
- lodgmentcalendar/_version.py +1 -0
- lodgmentcalendar/due_dates.py +259 -0
- lodgmentcalendar/holidays.py +127 -0
- lodgmentcalendar/py.typed +0 -0
- lodgmentcalendar/returns.py +300 -0
- lodgmentcalendar/rules.py +225 -0
- lodgmentcalendar/statements.py +133 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lodgment-calendar
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: ATO lodgment program due dates for BAS, IAS and income tax returns, with cited rules and UNKNOWN for missing facts. Preparation aid, not a determination.
|
|
5
|
+
Author: Ryan Duguid
|
|
6
|
+
License-Expression: FSL-1.1-ALv2
|
|
7
|
+
Project-URL: Homepage, https://github.com/ryanduguid/australian-accounting/tree/main/packages/lodgment-calendar
|
|
8
|
+
Project-URL: Repository, https://github.com/ryanduguid/australian-accounting/tree/main/packages/lodgment-calendar
|
|
9
|
+
Project-URL: Issues, https://github.com/ryanduguid/australian-accounting/issues
|
|
10
|
+
Keywords: australia,ato,lodgment-program,bas,tax-agent,bas-agent,due-dates
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
13
|
+
Classifier: Natural Language :: English
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Topic :: Office/Business :: Financial :: Accounting
|
|
18
|
+
Requires-Python: >=3.14
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
License-File: NOTICE
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: ruff==0.17.0; extra == "dev"
|
|
24
|
+
Requires-Dist: pyright[nodejs]==1.1.414; extra == "dev"
|
|
25
|
+
Requires-Dist: nodejs-wheel-binaries==24.19.0; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest==9.1.1; extra == "dev"
|
|
27
|
+
Requires-Dist: coverage==7.16.2; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest-cov==7.1.0; extra == "dev"
|
|
29
|
+
Requires-Dist: build==1.6.1; extra == "dev"
|
|
30
|
+
Requires-Dist: setuptools==84.2.1; extra == "dev"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# Lodgment calendar
|
|
34
|
+
|
|
35
|
+
`lodgment-calendar` works out lodgement and payment due dates for BAS, IAS and income tax
|
|
36
|
+
returns under the ATO's 2026–27 lodgment program, for self-lodgers and for clients of
|
|
37
|
+
registered tax and BAS agents. Each date carries its rule, the ATO page it comes from and
|
|
38
|
+
the date that page was checked. A date that depends on a fact you did not supply, or that
|
|
39
|
+
the ATO pages leave open, comes back `UNKNOWN` with the missing condition named.
|
|
40
|
+
|
|
41
|
+
Distribution `lodgment-calendar`, import package `lodgmentcalendar`. Library only: this
|
|
42
|
+
component ships no command.
|
|
43
|
+
|
|
44
|
+
**Package lifecycle:** published. Install `lodgment-calendar` from PyPI with
|
|
45
|
+
`pip install lodgment-calendar`; see [release notes](RELEASE_NOTES.md) and
|
|
46
|
+
[DISCLAIMER.md](DISCLAIMER.md).
|
|
47
|
+
|
|
48
|
+
It is a preparation aid. It reads the facts you pass, contacts no service, calls no ATO
|
|
49
|
+
API and lodges nothing. Ryan Duguid is not a registered tax agent or BAS agent. Project
|
|
50
|
+
support is limited to software issues reproduced with fabricated data. Do not send
|
|
51
|
+
taxpayer information.
|
|
52
|
+
|
|
53
|
+
## Example
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from lodgmentcalendar import Facts, due_dates
|
|
57
|
+
|
|
58
|
+
result = due_dates(
|
|
59
|
+
"bas", "company", "2026-27", "bas_agent", reporting_cycle="quarterly",
|
|
60
|
+
facts=Facts(consolidated_head_company=False, lodges_electronically=True,
|
|
61
|
+
previous_statement_lodged_electronically=True,
|
|
62
|
+
includes_monthly_payg_obligation=False),
|
|
63
|
+
)
|
|
64
|
+
for row in result["obligations"]:
|
|
65
|
+
print(row["period"], row["status"], row["nominal_due_date"], row["due_date"], row["rule_id"])
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
2026-27 Q1 DETERMINED 2026-11-25 2026-11-25 AS-Q-AGENT
|
|
70
|
+
2026-27 Q2 DETERMINED 2027-02-28 2027-03-02 AS-Q-28
|
|
71
|
+
2026-27 Q3 DETERMINED 2027-05-26 2027-05-26 AS-Q-AGENT
|
|
72
|
+
2026-27 Q4 UNKNOWN 2027-08-25 None AS-Q-AGENT
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Quarter 2 is due on Sunday 28 February 2027. Monday 1 March is Labour Day in Western
|
|
76
|
+
Australia, which moves the date for every state, so the statement is due on 2 March.
|
|
77
|
+
Quarter 4 is `UNKNOWN` because the ATO marks 25 August 2027 'to be confirmed'.
|
|
78
|
+
|
|
79
|
+
## Inputs
|
|
80
|
+
|
|
81
|
+
`due_dates(obligation, entity_type, financial_year, channel, *, reporting_cycle=None, facts=None, extra_holidays=None, holidays_complete_until=None)`
|
|
82
|
+
|
|
83
|
+
| Input | Values |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
| `obligation` | `bas`, `ias` or `income_tax_return` |
|
|
86
|
+
| `entity_type` | `individual`, `trust`, `partnership`, `company` or `smsf` |
|
|
87
|
+
| `financial_year` | The year of the statement periods, or the income year a return covers, as `2026-27` |
|
|
88
|
+
| `channel` | `self`, `tax_agent` or `bas_agent` (a BAS agent cannot lodge an income tax return) |
|
|
89
|
+
| `reporting_cycle` | `monthly` or `quarterly`, for `bas` and `ias` only. A monthly `bas` reports GST monthly; other monthly activity statements are `ias`. For a consolidated head company, a quarterly `ias` is its PAYG instalment activity statement |
|
|
90
|
+
| `facts` | A `Facts` with the conditions below; each is `True`, `False` or `None` (not supplied) |
|
|
91
|
+
| `extra_holidays` | Up to 50 `date: name` holidays the ATO table does not list (see below) |
|
|
92
|
+
| `holidays_complete_until` | A `date` to which the ATO table and `extra_holidays` hold every holiday (see below) |
|
|
93
|
+
|
|
94
|
+
This release records statement periods in 2026-27 and income tax returns for the 2025-26
|
|
95
|
+
income year, which are the returns the 2026–27 program covers. Any other year returns one
|
|
96
|
+
`UNKNOWN` row.
|
|
97
|
+
|
|
98
|
+
The `Facts` fields are the conditions the ATO pages make the dates depend on:
|
|
99
|
+
`prior_returns_outstanding`, `overdue_returns_lodged_by_31_october`,
|
|
100
|
+
`on_agent_client_list_by_cutoff`, `large_or_medium`, `member_large_or_medium`,
|
|
101
|
+
`new_registrant`, `prior_year_return_taxable`, `current_year_return_taxable`,
|
|
102
|
+
`latest_tax_liability_20k_or_more`, `total_income_over_2m`,
|
|
103
|
+
`prior_year_non_taxable_or_refund`, `current_year_non_taxable_or_refund`,
|
|
104
|
+
`consolidated_head_company`, `first_consolidated_return`, `lodges_electronically`,
|
|
105
|
+
`previous_statement_lodged_electronically`, `includes_monthly_payg_obligation`,
|
|
106
|
+
`annual_turnover_up_to_10m` and
|
|
107
|
+
`deferred_gst_registered`. `FACT_MEANINGS` in `lodgmentcalendar.due_dates` gives the
|
|
108
|
+
ATO's wording for each. Only the facts on a date's decision path are read; a `None` on
|
|
109
|
+
that path makes the date `UNKNOWN`.
|
|
110
|
+
|
|
111
|
+
Invalid inputs raise `ValueError`: an unknown value, a malformed year, a cycle on an
|
|
112
|
+
income tax return, a BAS agent lodging an income tax return or a fact that is not
|
|
113
|
+
`True`, `False` or `None`.
|
|
114
|
+
|
|
115
|
+
## Output
|
|
116
|
+
|
|
117
|
+
The result holds `obligations`, `counts` by status, `request`, `facts_used`,
|
|
118
|
+
`holiday_calendar`, `not_decided`, `warnings` and `source_checked`. Each obligation row has:
|
|
119
|
+
|
|
120
|
+
| Field | Meaning |
|
|
121
|
+
| --- | --- |
|
|
122
|
+
| `kind` | `lodgment`, `payment` (only where the payment date differs) or `concession` |
|
|
123
|
+
| `status` | `DETERMINED` or `UNKNOWN` |
|
|
124
|
+
| `due_date` | The date after the next-business-day roll; `null` when `UNKNOWN` |
|
|
125
|
+
| `nominal_due_date` | The date the rule names, before the roll, where the rule names one |
|
|
126
|
+
| `rule_id`, `summary` | The rule applied, in plain English |
|
|
127
|
+
| `source_url`, `source_provision`, `source_checked` | The ATO page, the table or heading on it, and the review date |
|
|
128
|
+
| `holiday_roll` | The weekend days and holidays passed over, with the holiday table's URL |
|
|
129
|
+
| `missing` | Each fact not supplied or question the ATO pages leave open |
|
|
130
|
+
| `payment` | On lodgement rows: `with_lodgment`, `separate`, `none` or `unknown` |
|
|
131
|
+
|
|
132
|
+
## Weekends and public holidays
|
|
133
|
+
|
|
134
|
+
The holiday calendar is the ATO's own: Tables 1 and 2 of
|
|
135
|
+
[Lodgment and payment dates on weekends or public holidays](https://www.ato.gov.au/tax-and-super-professionals/for-tax-professionals/prepare-and-lodge/registered-agent-lodgment-program/lodgment-and-payment-dates-on-weekends-or-public-holidays),
|
|
136
|
+
checked on 20 October 2026 and again on 28 October 2026. Under that page, a date that
|
|
137
|
+
falls on a Saturday, a Sunday or a public holiday for the whole of any state or territory
|
|
138
|
+
moves to the next business day for taxpayers in every state. The tables cover 1 July 2026 to 30 June 2027. A later date
|
|
139
|
+
that is not a weekend cannot be shown to be a business day, so it is `UNKNOWN` with its
|
|
140
|
+
nominal date kept. A holiday missing from the tables can only move a date later.
|
|
141
|
+
|
|
142
|
+
To settle those dates, pass the missing holidays from an official source as
|
|
143
|
+
`extra_holidays` and the last date your list is complete to as
|
|
144
|
+
`holidays_complete_until`. Holidays alone never extend coverage, because a list holding
|
|
145
|
+
one 2027 holiday is not a list that has 2027 covered. Each one must be a weekday the ATO
|
|
146
|
+
table does not already list, and shows in `holiday_roll` and `holiday_calendar` marked
|
|
147
|
+
'(caller-supplied)'.
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from datetime import date
|
|
151
|
+
|
|
152
|
+
result = due_dates("bas", "trust", "2026-27", "self", reporting_cycle="quarterly",
|
|
153
|
+
facts=Facts(lodges_electronically=True),
|
|
154
|
+
holidays_complete_until=date(2027, 8, 31))
|
|
155
|
+
print(result["obligations"][3]["due_date"])
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
2027-08-11
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
This assumes no whole-of-state holiday falls between 1 July and 31 August 2027 beyond
|
|
163
|
+
those you pass; the calendar takes that statement as given.
|
|
164
|
+
|
|
165
|
+
## Scope
|
|
166
|
+
|
|
167
|
+
Modelled rules, open questions and every source are listed in the
|
|
168
|
+
[primary-source review](docs/primary-source-review-2026-10-20.md). The calendar does not
|
|
169
|
+
model substituted accounting periods, companies that are not full self-assessment
|
|
170
|
+
taxpayers, APRA-regulated funds, annual GST returns, instalment notices, FBT or other
|
|
171
|
+
obligation types. It does not check whether the facts you pass are true, apply a
|
|
172
|
+
client-specific date the ATO has set, or decide whether anything was lodged or paid on
|
|
173
|
+
time.
|
|
174
|
+
|
|
175
|
+
## Licence
|
|
176
|
+
|
|
177
|
+
Licensed under the Functional Source License, Version 1.1, ALv2 Future License (FSL-1.1-ALv2). You may use, modify and redistribute it for any purpose except a Competing Use as defined in LICENSE (making it available to others in a competing commercial product or service), and each version becomes available under Apache-2.0 two years after it is made available. See [LICENSE](LICENSE) and [NOTICE](NOTICE).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
lodgment_calendar-0.1.0.dist-info/licenses/LICENSE,sha256=6AxWPlVU_Ce-vmqUDCqY10KJKh33AR3QTazuNEN7uVw,3743
|
|
2
|
+
lodgment_calendar-0.1.0.dist-info/licenses/NOTICE,sha256=ptxdTJjsS5yPxA8ju2a_0ST7Nxa5m_rf2dxfLhAa6Hw,866
|
|
3
|
+
lodgmentcalendar/__init__.py,sha256=3bTglzXSYKD0PQWJotU1ODC9zqwlATVIpyk_TsaXA4Q,326
|
|
4
|
+
lodgmentcalendar/_version.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
|
|
5
|
+
lodgmentcalendar/due_dates.py,sha256=JoveKeNFkTDNt8F8IEi0gGZae4ft_4439l02DXLJ0rY,12023
|
|
6
|
+
lodgmentcalendar/holidays.py,sha256=69zzh314Q1dMO-1UzQ1QhfHTHinHNbauN6TNZRtRNU8,6389
|
|
7
|
+
lodgmentcalendar/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
lodgmentcalendar/returns.py,sha256=HVt6oHTp_k6e5Uduhpwhsg8LsbK_kDtFa-yVtUfIQ7o,17439
|
|
9
|
+
lodgmentcalendar/rules.py,sha256=XUfbMZkDFo8bmlYiiGs2IDKUqnsdaAFtPJK1LbJAD8M,10633
|
|
10
|
+
lodgmentcalendar/statements.py,sha256=qeHB9hVlcAnTWtAAKPPCXw3wwEaz0mOMzquNxIizCIE,7517
|
|
11
|
+
lodgment_calendar-0.1.0.dist-info/METADATA,sha256=5GUGGxkx-M486srDOGUQjuhr2ElQjHEHWlod2gtkgv8,9476
|
|
12
|
+
lodgment_calendar-0.1.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
13
|
+
lodgment_calendar-0.1.0.dist-info/top_level.txt,sha256=YCVyBC-sGD3tftQbTV78AUZbDbrrUYtQJ3w1urQPg_0,17
|
|
14
|
+
lodgment_calendar-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Functional Source License, Version 1.1, ALv2 Future License
|
|
2
|
+
|
|
3
|
+
## Abbreviation
|
|
4
|
+
|
|
5
|
+
FSL-1.1-ALv2
|
|
6
|
+
|
|
7
|
+
## Notice
|
|
8
|
+
|
|
9
|
+
Copyright 2026 Ryan Duguid
|
|
10
|
+
|
|
11
|
+
## Terms and Conditions
|
|
12
|
+
|
|
13
|
+
### Licensor ("We")
|
|
14
|
+
|
|
15
|
+
The party offering the Software under these Terms and Conditions.
|
|
16
|
+
|
|
17
|
+
### The Software
|
|
18
|
+
|
|
19
|
+
The "Software" is each version of the software that we make available under
|
|
20
|
+
these Terms and Conditions, as indicated by our inclusion of these Terms and
|
|
21
|
+
Conditions with the Software.
|
|
22
|
+
|
|
23
|
+
### License Grant
|
|
24
|
+
|
|
25
|
+
Subject to your compliance with this License Grant and the Patents,
|
|
26
|
+
Redistribution and Trademark clauses below, we hereby grant you the right to
|
|
27
|
+
use, copy, modify, create derivative works, publicly perform, publicly display
|
|
28
|
+
and redistribute the Software for any Permitted Purpose identified below.
|
|
29
|
+
|
|
30
|
+
### Permitted Purpose
|
|
31
|
+
|
|
32
|
+
A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
|
|
33
|
+
means making the Software available to others in a commercial product or
|
|
34
|
+
service that:
|
|
35
|
+
|
|
36
|
+
1. substitutes for the Software;
|
|
37
|
+
|
|
38
|
+
2. substitutes for any other product or service we offer using the Software
|
|
39
|
+
that exists as of the date we make the Software available; or
|
|
40
|
+
|
|
41
|
+
3. offers the same or substantially similar functionality as the Software.
|
|
42
|
+
|
|
43
|
+
Permitted Purposes specifically include using the Software:
|
|
44
|
+
|
|
45
|
+
1. for your internal use and access;
|
|
46
|
+
|
|
47
|
+
2. for non-commercial education;
|
|
48
|
+
|
|
49
|
+
3. for non-commercial research; and
|
|
50
|
+
|
|
51
|
+
4. in connection with professional services that you provide to a licensee
|
|
52
|
+
using the Software in accordance with these Terms and Conditions.
|
|
53
|
+
|
|
54
|
+
### Patents
|
|
55
|
+
|
|
56
|
+
To the extent your use for a Permitted Purpose would necessarily infringe our
|
|
57
|
+
patents, the license grant above includes a license under our patents. If you
|
|
58
|
+
make a claim against any party that the Software infringes or contributes to
|
|
59
|
+
the infringement of any patent, then your patent license to the Software ends
|
|
60
|
+
immediately.
|
|
61
|
+
|
|
62
|
+
### Redistribution
|
|
63
|
+
|
|
64
|
+
The Terms and Conditions apply to all copies, modifications and derivatives of
|
|
65
|
+
the Software.
|
|
66
|
+
|
|
67
|
+
If you redistribute any copies, modifications or derivatives of the Software,
|
|
68
|
+
you must include a copy of or a link to these Terms and Conditions and not
|
|
69
|
+
remove any copyright notices provided in or with the Software.
|
|
70
|
+
|
|
71
|
+
### Disclaimer
|
|
72
|
+
|
|
73
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
|
|
74
|
+
IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
|
|
75
|
+
PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
|
|
76
|
+
|
|
77
|
+
IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
|
|
78
|
+
SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
|
|
79
|
+
EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
|
|
80
|
+
|
|
81
|
+
### Trademarks
|
|
82
|
+
|
|
83
|
+
Except for displaying the License Details and identifying us as the origin of
|
|
84
|
+
the Software, you have no right under these Terms and Conditions to use our
|
|
85
|
+
trademarks, trade names, service marks or product names.
|
|
86
|
+
|
|
87
|
+
## Grant of Future License
|
|
88
|
+
|
|
89
|
+
We hereby irrevocably grant you an additional license to use the Software under
|
|
90
|
+
the Apache License, Version 2.0 that is effective on the second anniversary of
|
|
91
|
+
the date we make the Software available. On or after that date, you may use the
|
|
92
|
+
Software under the Apache License, Version 2.0, in which case the following
|
|
93
|
+
will apply:
|
|
94
|
+
|
|
95
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
96
|
+
this file except in compliance with the License.
|
|
97
|
+
|
|
98
|
+
You may obtain a copy of the License at
|
|
99
|
+
|
|
100
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
101
|
+
|
|
102
|
+
Unless required by applicable law or agreed to in writing, software distributed
|
|
103
|
+
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
104
|
+
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
105
|
+
specific language governing permissions and limitations under the License.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
lodgment-calendar
|
|
2
|
+
Copyright 2026 Ryan Duguid (https://github.com/ryanduguid)
|
|
3
|
+
|
|
4
|
+
This software is licensed under the Functional Source License,
|
|
5
|
+
Version 1.1, ALv2 Future License (FSL-1.1-ALv2); see LICENSE.
|
|
6
|
+
|
|
7
|
+
For each version licensed under FSL-1.1-ALv2, an additional licence under the
|
|
8
|
+
Apache License, Version 2.0 becomes effective on the second anniversary of the
|
|
9
|
+
date that version was made available.
|
|
10
|
+
|
|
11
|
+
Versions made available before this change remain available under the
|
|
12
|
+
licences they were released with.
|
|
13
|
+
|
|
14
|
+
Redistribution under FSL-1.1-ALv2 requires including its terms or a link to
|
|
15
|
+
them and keeping the copyright notices supplied with the software.
|
|
16
|
+
Redistribution under another applicable licence follows that licence's
|
|
17
|
+
conditions.
|
|
18
|
+
|
|
19
|
+
This product includes work by Ryan Duguid
|
|
20
|
+
(https://github.com/ryanduguid/australian-accounting/tree/main/packages/lodgment-calendar).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
lodgmentcalendar
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Lodgment and payment due dates under the ATO's 2026–27 lodgment program.
|
|
2
|
+
|
|
3
|
+
A preparation aid, not a determination: it lodges nothing and contacts no service.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from ._version import __version__
|
|
9
|
+
from .due_dates import Facts, due_dates
|
|
10
|
+
|
|
11
|
+
__all__ = ["Facts", "__version__", "due_dates"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"""Lodgment and payment due dates under the ATO's 2026–27 lodgment program.
|
|
2
|
+
|
|
3
|
+
The calendar reads the facts the caller supplies, applies the dates the ATO
|
|
4
|
+
publishes for self-lodgers and for registered tax and BAS agents, and moves each
|
|
5
|
+
date to the next business day under the program's public holiday rule. A date
|
|
6
|
+
that depends on a fact the caller did not supply, or that the ATO pages leave
|
|
7
|
+
open, is UNKNOWN and names what is missing. It contacts no service and lodges
|
|
8
|
+
nothing.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import re
|
|
14
|
+
from collections.abc import Mapping
|
|
15
|
+
from dataclasses import fields
|
|
16
|
+
from datetime import date
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from ._version import __version__
|
|
20
|
+
from .holidays import COVERS_FROM, HOLIDAY_PROVISION, HOLIDAY_SOURCE, calendar, roll
|
|
21
|
+
from .returns import return_rule
|
|
22
|
+
from .rules import FACT_MEANINGS, SOURCES, Facts, Rule
|
|
23
|
+
from .statements import statement_rules
|
|
24
|
+
|
|
25
|
+
SOURCE_CHECKED = "2026-10-28"
|
|
26
|
+
PROGRAM_YEAR = "2026-27"
|
|
27
|
+
# The program dates cover activity statements for periods in 2026–27 and income
|
|
28
|
+
# tax returns for the 2025–26 income year (the 2026 return).
|
|
29
|
+
ACTIVITY_YEAR = "2026-27"
|
|
30
|
+
RETURN_YEAR = "2025-26"
|
|
31
|
+
|
|
32
|
+
DETERMINED, UNKNOWN = "DETERMINED", "UNKNOWN"
|
|
33
|
+
OBLIGATIONS = ("bas", "ias", "income_tax_return")
|
|
34
|
+
ENTITY_TYPES = ("individual", "trust", "partnership", "company", "smsf")
|
|
35
|
+
CHANNELS = ("self", "tax_agent", "bas_agent")
|
|
36
|
+
CYCLES = ("monthly", "quarterly")
|
|
37
|
+
|
|
38
|
+
NOT_DECIDED = [
|
|
39
|
+
"Dates the ATO sets for one client override these: a notice requiring earlier "
|
|
40
|
+
"lodgement, a prosecution letter, a deferral, an advised first-year date for a new "
|
|
41
|
+
"SMSF, or the due date shown on the activity statement or in Online services for "
|
|
42
|
+
"agents.",
|
|
43
|
+
"Whether a fact supplied is true. Large or medium status, taxable status, income and "
|
|
44
|
+
"liability thresholds, registration and lodgement history are taken as given.",
|
|
45
|
+
"Substituted accounting periods, companies that are not full self-assessment "
|
|
46
|
+
"taxpayers, APRA-regulated funds, members exiting a consolidated group, annual GST "
|
|
47
|
+
"returns, instalment notices (forms R, S, T and N), PAYG instalments paid in 2 "
|
|
48
|
+
"instalments or annually, deferred BAS payers (who pay monthly PAYG instalments on the "
|
|
49
|
+
"28th) and every other obligation type.",
|
|
50
|
+
"Whether a lodgement or payment was on time, and any penalty or general interest charge.",
|
|
51
|
+
"Disaster or other deferrals, and holidays proclaimed after the ATO published its "
|
|
52
|
+
"2026–27 table.",
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
WARNINGS = [
|
|
56
|
+
"The ATO says its list of lodgment program due dates is a guide only. Confirm each "
|
|
57
|
+
"client's dates in Online services for agents or the practitioner lodgment service.",
|
|
58
|
+
"Retain the status and missing conditions of every UNKNOWN row; do not replace one "
|
|
59
|
+
"with a nominal date.",
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
_YEAR = re.compile(r"([0-9]{4})-([0-9]{2})")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def due_dates(
|
|
66
|
+
obligation: str,
|
|
67
|
+
entity_type: str,
|
|
68
|
+
financial_year: str,
|
|
69
|
+
channel: str,
|
|
70
|
+
*,
|
|
71
|
+
reporting_cycle: str | None = None,
|
|
72
|
+
facts: Facts | None = None,
|
|
73
|
+
extra_holidays: Mapping[date, str] | None = None,
|
|
74
|
+
holidays_complete_until: date | None = None,
|
|
75
|
+
) -> dict[str, Any]:
|
|
76
|
+
"""Return each lodgement, payment and concession date with its rule and status.
|
|
77
|
+
|
|
78
|
+
``financial_year`` is the year of the periods (activity statements) or the income
|
|
79
|
+
year the return covers (income tax returns), in the form 2026-27.
|
|
80
|
+
|
|
81
|
+
``extra_holidays`` adds public holidays for the whole of a state or territory that
|
|
82
|
+
the ATO table does not list, by date and name. ``holidays_complete_until`` states
|
|
83
|
+
that the ATO table and those holidays hold every such holiday to that date, which
|
|
84
|
+
lets dates after 30 June 2027 be settled.
|
|
85
|
+
"""
|
|
86
|
+
facts = Facts() if facts is None else facts
|
|
87
|
+
_check(obligation, entity_type, financial_year, channel, reporting_cycle, facts)
|
|
88
|
+
cal = calendar(extra_holidays, holidays_complete_until)
|
|
89
|
+
if obligation == "income_tax_return":
|
|
90
|
+
rows = _return_rows(entity_type, financial_year, channel, facts, cal)
|
|
91
|
+
else:
|
|
92
|
+
rows = _statement_rows(obligation, entity_type, financial_year, channel,
|
|
93
|
+
reporting_cycle, facts, cal)
|
|
94
|
+
return {
|
|
95
|
+
"ok": True,
|
|
96
|
+
"engine": "lodgment-calendar",
|
|
97
|
+
"engine_version": __version__,
|
|
98
|
+
"calendar": "lodgment_due_dates",
|
|
99
|
+
"program_year": PROGRAM_YEAR,
|
|
100
|
+
"request": {"obligation": obligation, "entity_type": entity_type,
|
|
101
|
+
"financial_year": financial_year, "channel": channel,
|
|
102
|
+
"reporting_cycle": reporting_cycle},
|
|
103
|
+
"facts_used": {field.name: getattr(facts, field.name) for field in fields(facts)},
|
|
104
|
+
"obligations": rows,
|
|
105
|
+
"counts": {status: sum(1 for row in rows if row["status"] == status)
|
|
106
|
+
for status in (DETERMINED, UNKNOWN)},
|
|
107
|
+
"holiday_calendar": _holiday_calendar(cal, holidays_complete_until),
|
|
108
|
+
"not_decided": NOT_DECIDED,
|
|
109
|
+
"warnings": WARNINGS,
|
|
110
|
+
"source_checked": SOURCE_CHECKED,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _return_rows(entity_type: str, financial_year: str, channel: str, facts: Facts,
|
|
115
|
+
cal: Calendar) -> list[dict[str, Any]]:
|
|
116
|
+
label = f"Income tax return {financial_year}"
|
|
117
|
+
if financial_year != RETURN_YEAR:
|
|
118
|
+
return _rows(label, financial_year, _not_recorded(financial_year, RETURN_YEAR), [],
|
|
119
|
+
cal=cal)
|
|
120
|
+
rule, missing, concessions = return_rule(entity_type, channel, facts)
|
|
121
|
+
# A partnership pays no income tax, whatever its lodgement date turns out to be.
|
|
122
|
+
rows = _rows(label, financial_year, rule, missing, family=_family(entity_type, channel),
|
|
123
|
+
pending_payment="none" if entity_type == "partnership" else "unknown",
|
|
124
|
+
cal=cal)
|
|
125
|
+
for concession, needed in concessions:
|
|
126
|
+
rows.append(_row(label, financial_year, "concession", concession, needed, cal=cal))
|
|
127
|
+
return rows
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _statement_rows(obligation: str, entity_type: str, financial_year: str, channel: str,
|
|
131
|
+
reporting_cycle: str | None, facts: Facts,
|
|
132
|
+
cal: Calendar) -> list[dict[str, Any]]:
|
|
133
|
+
if financial_year != ACTIVITY_YEAR:
|
|
134
|
+
return _rows(f"{obligation.upper()} {financial_year}", financial_year,
|
|
135
|
+
_not_recorded(financial_year, ACTIVITY_YEAR), [], cal=cal)
|
|
136
|
+
rows: list[dict[str, Any]] = []
|
|
137
|
+
for period, label, rule, missing in statement_rules(
|
|
138
|
+
obligation, entity_type, channel, reporting_cycle, facts):
|
|
139
|
+
rows += _rows(label, period, rule, missing, family=_family(obligation, channel),
|
|
140
|
+
cal=cal)
|
|
141
|
+
return rows
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _holiday_calendar(cal: Calendar, complete_until: date | None) -> dict[str, Any]:
|
|
145
|
+
return {
|
|
146
|
+
"definition": "Saturdays, Sundays and days that are a public holiday for the whole "
|
|
147
|
+
"of any state or territory move a due date to the next business day, for taxpayers "
|
|
148
|
+
"in every state.",
|
|
149
|
+
"source_url": HOLIDAY_SOURCE,
|
|
150
|
+
"source_provision": HOLIDAY_PROVISION,
|
|
151
|
+
"covers_from": COVERS_FROM.isoformat(),
|
|
152
|
+
"covers_to": cal[1].isoformat(),
|
|
153
|
+
"caller_holidays": {day.isoformat(): name for day, name in sorted(cal[0].items())
|
|
154
|
+
if name.endswith(" (caller-supplied)")},
|
|
155
|
+
"caller_complete_until": None if complete_until is None else complete_until.isoformat(),
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _check(obligation: str, entity_type: str, financial_year: object, channel: str,
|
|
160
|
+
reporting_cycle: str | None, facts: object) -> None:
|
|
161
|
+
for name, value, allowed in (("obligation", obligation, OBLIGATIONS),
|
|
162
|
+
("entity_type", entity_type, ENTITY_TYPES),
|
|
163
|
+
("channel", channel, CHANNELS)):
|
|
164
|
+
if value not in allowed:
|
|
165
|
+
raise ValueError(f"{name} must be one of {', '.join(allowed)}; got {value!r}")
|
|
166
|
+
match = _YEAR.fullmatch(financial_year) if isinstance(financial_year, str) else None
|
|
167
|
+
if match is None or int(match[2]) != (int(match[1]) + 1) % 100:
|
|
168
|
+
raise ValueError(f"financial_year must look like 2026-27; got {financial_year!r}")
|
|
169
|
+
if obligation == "income_tax_return":
|
|
170
|
+
if reporting_cycle is not None:
|
|
171
|
+
raise ValueError("reporting_cycle applies only to bas and ias")
|
|
172
|
+
if channel == "bas_agent":
|
|
173
|
+
raise ValueError("a BAS agent cannot lodge an income tax return; use tax_agent")
|
|
174
|
+
elif reporting_cycle not in CYCLES:
|
|
175
|
+
raise ValueError(f"reporting_cycle must be one of {', '.join(CYCLES)} for "
|
|
176
|
+
f"{obligation}; got {reporting_cycle!r}")
|
|
177
|
+
if not isinstance(facts, Facts):
|
|
178
|
+
raise ValueError("facts must be a Facts instance")
|
|
179
|
+
for field in fields(facts):
|
|
180
|
+
value = getattr(facts, field.name)
|
|
181
|
+
# Identity, not isinstance: an object can claim bool as its class.
|
|
182
|
+
if value is not None and value is not True and value is not False:
|
|
183
|
+
raise ValueError(f"facts.{field.name} must be true, false or null; got {value!r}")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _family(kind: str, channel: str) -> str:
|
|
187
|
+
return f"{kind.upper()}-{'SELF' if channel == 'self' else 'AGENT'}"
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _not_recorded(year: str, recorded: str) -> Rule:
|
|
191
|
+
return Rule(
|
|
192
|
+
"YEAR-NOT-RECORDED",
|
|
193
|
+
f"This release records the {PROGRAM_YEAR} lodgment program, which covers {recorded} "
|
|
194
|
+
f"for this obligation; {year} is not recorded.",
|
|
195
|
+
"about",
|
|
196
|
+
unresolved=f"rules for {year}: this release records only {recorded} for this "
|
|
197
|
+
"obligation",
|
|
198
|
+
payment="unknown",
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
Calendar = tuple[dict[date, str], date]
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _rows(label: str, period: str, rule: Rule | None, missing: list[str], *, cal: Calendar,
|
|
206
|
+
family: str = "", pending_payment: str = "unknown") -> list[dict[str, Any]]:
|
|
207
|
+
if rule is None or missing:
|
|
208
|
+
pending = Rule(family, "The date depends on facts not supplied: "
|
|
209
|
+
+ "; ".join(FACT_MEANINGS[name] for name in missing) + ".", "about")
|
|
210
|
+
return [_row(label, period, "lodgment", pending, missing, payment=pending_payment,
|
|
211
|
+
cal=cal)]
|
|
212
|
+
payment = rule.payment
|
|
213
|
+
rows = [_row(label, period, "lodgment", rule, [],
|
|
214
|
+
payment=payment if isinstance(payment, str) else "separate", cal=cal)]
|
|
215
|
+
if isinstance(payment, Rule):
|
|
216
|
+
rows.append(_row(label, period, "payment", payment, [], cal=cal))
|
|
217
|
+
return rows
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _row(label: str, period: str, kind: str, rule: Rule, missing: list[str], *,
|
|
221
|
+
cal: Calendar, payment: str | None = None) -> dict[str, Any]:
|
|
222
|
+
url, provision = SOURCES[rule.source]
|
|
223
|
+
needed = [f"{name}: {FACT_MEANINGS[name]}" for name in missing]
|
|
224
|
+
due: date | None = None
|
|
225
|
+
holiday_roll = None
|
|
226
|
+
if rule.unresolved is not None:
|
|
227
|
+
needed.append(rule.unresolved)
|
|
228
|
+
elif rule.nominal is not None and not needed:
|
|
229
|
+
due, skipped, open_question = roll(rule.nominal, *cal)
|
|
230
|
+
if skipped or due is None:
|
|
231
|
+
holiday_roll = {"skipped": skipped, "source_url": HOLIDAY_SOURCE}
|
|
232
|
+
if open_question is not None:
|
|
233
|
+
needed.append(open_question)
|
|
234
|
+
elif kind == "concession" and skipped:
|
|
235
|
+
needed.append(
|
|
236
|
+
f"whether the next-business-day rule extends this concession: "
|
|
237
|
+
f"{rule.nominal.isoformat()} is not a business day, and the ATO pages call "
|
|
238
|
+
"it both a concessional due date and not a lodgment due date")
|
|
239
|
+
due = None
|
|
240
|
+
if needed:
|
|
241
|
+
due = None
|
|
242
|
+
row: dict[str, Any] = {
|
|
243
|
+
"obligation": label,
|
|
244
|
+
"period": period,
|
|
245
|
+
"kind": kind,
|
|
246
|
+
"status": UNKNOWN if needed else DETERMINED,
|
|
247
|
+
"due_date": None if due is None else due.isoformat(),
|
|
248
|
+
"nominal_due_date": None if rule.nominal is None else rule.nominal.isoformat(),
|
|
249
|
+
"rule_id": rule.id,
|
|
250
|
+
"summary": rule.summary,
|
|
251
|
+
"source_url": url,
|
|
252
|
+
"source_provision": provision,
|
|
253
|
+
"source_checked": SOURCE_CHECKED,
|
|
254
|
+
"holiday_roll": holiday_roll,
|
|
255
|
+
"missing": needed,
|
|
256
|
+
}
|
|
257
|
+
if kind == "lodgment":
|
|
258
|
+
row["payment"] = payment
|
|
259
|
+
return row
|