stackin-python-sdk 0.1.1__tar.gz → 0.1.2__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stackin-python-sdk
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Python SDK for issuing, consulting and cancelling electronic invoices.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -104,5 +104,5 @@ Building the full fiscal document (issuer data, service code, tax groups, schema
104
104
 
105
105
  ## Examples
106
106
 
107
- Runnable end-to-end scripts in [`examples/`](examples/) `simple_issue_nfe.py` and `simple_issue_nfse.py`, each with a catalog of realistic line items covering every optional field.
107
+ Runnable end-to-end scripts in [`examples/nfe/`](examples/nfe/) and [`examples/nfse/`](examples/nfse/) one file per field/variant, from the bare minimum to every field filled.
108
108
 
@@ -75,4 +75,4 @@ Building the full fiscal document (issuer data, service code, tax groups, schema
75
75
 
76
76
  ## Examples
77
77
 
78
- Runnable end-to-end scripts in [`examples/`](examples/) `simple_issue_nfe.py` and `simple_issue_nfse.py`, each with a catalog of realistic line items covering every optional field.
78
+ Runnable end-to-end scripts in [`examples/nfe/`](examples/nfe/) and [`examples/nfse/`](examples/nfse/) one file per field/variant, from the bare minimum to every field filled.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "stackin-python-sdk"
3
- version = "0.1.1"
3
+ version = "0.1.2"
4
4
  authors = [
5
5
  { name = "Fernando Celmer", email = "email@fernandocelmer.com" },
6
6
  ]
@@ -45,7 +45,7 @@ Changelog = "https://github.com/stackin-io/stackin-python-sdk/releases"
45
45
 
46
46
  [tool.poetry]
47
47
  name = "stackin-python-sdk"
48
- version = "0.1.1"
48
+ version = "0.1.2"
49
49
  description = "Python SDK for issuing, consulting and cancelling electronic invoices."
50
50
  authors = ["Fernando Celmer <email@fernandocelmer.com>"]
51
51
  readme = "README.md"
@@ -1,6 +1,6 @@
1
1
  """Invoice __init__ module."""
2
2
 
3
- __version__ = "0.1.1"
3
+ __version__ = "0.1.2"
4
4
  __description__ = (
5
5
  "Python SDK for issuing, consulting and cancelling electronic invoices."
6
6
  )
@@ -10,6 +10,7 @@ from stackin.br.tax import (
10
10
  Icms60,
11
11
  IcmsSn101,
12
12
  IcmsSn102,
13
+ IcmsSn900,
13
14
  IcmsUfDest,
14
15
  Ipi,
15
16
  IpiNt,
@@ -29,6 +30,7 @@ __all__ = [
29
30
  "Icms60",
30
31
  "IcmsSn101",
31
32
  "IcmsSn102",
33
+ "IcmsSn900",
32
34
  "IcmsUfDest",
33
35
  "Ipi",
34
36
  "IpiTrib",
@@ -73,11 +73,36 @@ class Product(BaseModel):
73
73
  extra_groups: dict[str, Any] | None = Field(default=None)
74
74
  tax: Tax | dict[str, Any] | None = Field(default=None)
75
75
 
76
+ service_code: str | None = Field(
77
+ default=None, description="LC 116/2003 item.subitem, nfse only."
78
+ )
79
+ service_discount: float | None = Field(
80
+ default=None, description="Unconditional discount, nfse only."
81
+ )
82
+ tax_retained: bool = Field(
83
+ default=False, description="ISSQN retained by the tomador, nfse only."
84
+ )
85
+ observations: str | None = Field(
86
+ default=None, max_length=2000, description="nfse only."
87
+ )
88
+
76
89
  def to_dict(self) -> dict:
77
90
  """Returns the item as a plain dict, ready for the request body."""
91
+ _NFSE_FIELDS = {
92
+ "service_code",
93
+ "service_discount",
94
+ "tax_retained",
95
+ "observations",
96
+ }
78
97
  data = self.model_dump(
79
98
  exclude_none=True,
80
- exclude={"description", "amount", "tax", *_BR_FIELDS},
99
+ exclude={
100
+ "description",
101
+ "amount",
102
+ "tax",
103
+ *_BR_FIELDS,
104
+ *_NFSE_FIELDS,
105
+ },
81
106
  )
82
107
  br = self.model_dump(exclude_none=True, include=_BR_FIELDS - {"tax"})
83
108
  if isinstance(self.tax, Tax):
@@ -90,4 +115,8 @@ class Product(BaseModel):
90
115
  "description": self.description,
91
116
  "amount": self.amount,
92
117
  "product": data,
118
+ "service_code": self.service_code,
119
+ "discount": self.service_discount,
120
+ "tax_retained": self.tax_retained,
121
+ "observations": self.observations,
93
122
  }
@@ -77,6 +77,19 @@ class IcmsSn102(BaseModel):
77
77
  csosn: str = Field(alias="CSOSN")
78
78
 
79
79
 
80
+ class IcmsSn900(BaseModel):
81
+ """Simples Nacional ICMS, other cases (used with interstate partilha)."""
82
+
83
+ model_config = _CONFIG
84
+
85
+ orig: str | None = None
86
+ csosn: str = Field(default="900", alias="CSOSN")
87
+ mod_bc: str | None = Field(default=None, alias="modBC")
88
+ v_bc: str | None = Field(default=None, alias="vBC")
89
+ p_icms: str | None = Field(default=None, alias="pICMS")
90
+ v_icms: str | None = Field(default=None, alias="vICMS")
91
+
92
+
80
93
  class IcmsUfDest(BaseModel):
81
94
  """Interstate ICMS share owed to the destination state."""
82
95
 
@@ -193,7 +206,15 @@ class CofinsOutr(BaseModel):
193
206
  v_cofins: str = Field(alias="vCOFINS")
194
207
 
195
208
 
196
- IcmsGroup = Icms00 | Icms40 | Icms60 | IcmsSn101 | IcmsSn102 | dict[str, Any]
209
+ IcmsGroup = (
210
+ Icms00
211
+ | Icms40
212
+ | Icms60
213
+ | IcmsSn101
214
+ | IcmsSn102
215
+ | IcmsSn900
216
+ | dict[str, Any]
217
+ )
197
218
  PisGroup = PisAliq | PisNt | PisOutr | dict[str, Any]
198
219
  CofinsGroup = CofinsAliq | CofinsNt | CofinsOutr | dict[str, Any]
199
220
 
@@ -203,6 +224,7 @@ _ICMS_TAGS = {
203
224
  Icms60: "ICMS60",
204
225
  IcmsSn101: "ICMSSN101",
205
226
  IcmsSn102: "ICMSSN102",
227
+ IcmsSn900: "ICMSSN900",
206
228
  }
207
229
  _PIS_TAGS = {PisAliq: "PISAliq", PisNt: "PISNT", PisOutr: "PISOutr"}
208
230
  _COFINS_TAGS = {
@@ -23,9 +23,7 @@ _ENVIRONMENT_URLS = {
23
23
  def _resolve_base_url(
24
24
  base_url: str | None, environment: Environment | str | None
25
25
  ) -> str:
26
- """Resolution order, same shape as the AWS CLI: explicit param,
27
- then env var, then the environment's default — `base_url` always
28
- wins over `environment` at each step."""
26
+ """Resolution order: explicit param, then env var, then environment's default."""
29
27
  if base_url:
30
28
  return base_url
31
29
  if url := os.environ.get("STACKIN_BASE_URL"):
@@ -61,6 +59,8 @@ class Invoice:
61
59
  tax_id: str,
62
60
  items: list[Product],
63
61
  recipient_address: Address | None = None,
62
+ series: str | None = None,
63
+ number: str | None = None,
64
64
  ) -> dict:
65
65
  """Issues a fiscal document."""
66
66
  if not items:
@@ -81,8 +81,12 @@ class Invoice:
81
81
  "tax_id": tax_id,
82
82
  "items": [item.to_dict() for item in items],
83
83
  }
84
- if recipient_address and recipient_address.state:
85
- payload["recipient_state"] = recipient_address.state
84
+ if recipient_address:
85
+ payload["recipient_address"] = recipient_address.to_dict()
86
+ if series:
87
+ payload["series"] = series
88
+ if number:
89
+ payload["number"] = number
86
90
 
87
91
  return self._request("POST", "/invoices", json=payload)
88
92
 
@@ -15,5 +15,4 @@ class APIError(InvoiceError):
15
15
 
16
16
 
17
17
  class ConnectionFailedError(InvoiceError):
18
- """Raised when the invoice API can't be reached at all
19
- (network/DNS/timeout)."""
18
+ """Raised when the invoice API can't be reached at all (network/DNS/timeout)."""
@@ -13,10 +13,7 @@ class DocumentType(str, Enum):
13
13
 
14
14
 
15
15
  class Environment(str, Enum):
16
- """Which host to talk to — pass to `Invoice(environment=...)`
17
- instead of a raw `base_url`. `TEST` and `PRODUCTION` resolve to the
18
- same host: homologation vs. production invoicing is a per-company
19
- setting on the platform side, not a different SDK host."""
16
+ """Which host to talk to — pass to `Invoice(environment=...)` instead of a raw `base_url`."""
20
17
 
21
18
  LOCAL = "local"
22
19
  TEST = "test"