Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94ec414089 | ||
|
|
612821e363 |
@@ -36,6 +36,10 @@ organization grants that token read-only cross-repository access to
|
||||
|
||||
## Translation workflow
|
||||
|
||||
The complete Weblate architecture, translator procedure, promotion process,
|
||||
deployment instructions, troubleshooting, and recovery checklist are in
|
||||
[`dominican-translation-infrastructure.md`](dominican-translation-infrastructure.md).
|
||||
|
||||
The Tryton server owns `es_DO` as a standalone language; its database record
|
||||
does not declare `es_419` (or any other locale) as parent. Complete Dominican
|
||||
translations live beside each package in its regular `locale/es_DO.po`
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
# Dominican Spanish translation infrastructure
|
||||
|
||||
This document describes the production workflow for maintaining Spanish for
|
||||
the Dominican Republic (`es_DO`) in the customized Tryton repository. It is
|
||||
both an operating manual and a recovery reference.
|
||||
|
||||
## Design goals
|
||||
|
||||
- `es_DO` is a standalone Tryton language. It does not inherit from `es_419`.
|
||||
- Every maintained series has its own catalogs and translation branch.
|
||||
- Weblate never writes directly to a release branch.
|
||||
- Translation changes are reviewed and validated before promotion.
|
||||
- No password, personal access token, or API key is stored in Git.
|
||||
|
||||
## Components and ownership
|
||||
|
||||
| System | Responsibility |
|
||||
| --- | --- |
|
||||
| Official Tryton repository | Upstream source of Tryton changes. |
|
||||
| `tryton-do/tryton-mirror` | Read-only private mirror of upstream history. |
|
||||
| `tryton-do/tryton` | Customized source, Dominican catalogs, CI, and documentation. |
|
||||
| Gitea Actions | Validates catalogs, Python code, packages, and relevant tests. |
|
||||
| Weblate | Browser translation editor, checks, glossary, and translation memory. |
|
||||
| Gitea webhook | Tells Weblate that the source repository changed. |
|
||||
|
||||
The data flow is:
|
||||
|
||||
```text
|
||||
official upstream
|
||||
|
|
||||
v
|
||||
tryton-mirror ---- scheduled proposal ----> integration/8.0 ---- review ----> 8.0
|
||||
^
|
||||
|
|
||||
Weblate <---- webhook ---- tryton repository |
|
||||
| |
|
||||
+---- translation/weblate-8.0 ---- PR ------+
|
||||
```
|
||||
|
||||
The same isolated Weblate branch model is used for all maintained series. The
|
||||
8.0 production series adds the two-stage `integration/8.0` promotion gate.
|
||||
|
||||
## Version mapping
|
||||
|
||||
| Tryton series | Weblate project | Weblate Git branch | Promotion target |
|
||||
| --- | --- | --- | --- |
|
||||
| 6.0 | `tryton-6-0-es-do` | `translation/weblate-6.0` | `6.0` |
|
||||
| 7.0 | `tryton-7-0-es-do` | `translation/weblate-7.0` | `7.0` |
|
||||
| 8.0 | `tryton-8-0-es-do` | `translation/weblate-8.0` | `integration/8.0`, then `8.0` |
|
||||
| 8.2 | `tryton-8-2-es-do` | `translation/weblate-8.2` | `8.2` |
|
||||
|
||||
Project pages are available below:
|
||||
|
||||
- <https://translate.tryton.org.do/projects/tryton-6-0-es-do/>
|
||||
- <https://translate.tryton.org.do/projects/tryton-7-0-es-do/>
|
||||
- <https://translate.tryton.org.do/projects/tryton-8-0-es-do/>
|
||||
- <https://translate.tryton.org.do/projects/tryton-8-2-es-do/>
|
||||
|
||||
Each project contains independent components for the server core, server
|
||||
resources, SAO, and every Tryton module present in that series. Component
|
||||
discovery on the server-core component detects module catalogs matching:
|
||||
|
||||
```text
|
||||
modules/<module>/locale/es_DO.po
|
||||
```
|
||||
|
||||
The manually managed catalogs are:
|
||||
|
||||
```text
|
||||
trytond/trytond/ir/locale/es_DO.po
|
||||
trytond/trytond/res/locale/es_DO.po
|
||||
sao/locale/es_DO.po
|
||||
```
|
||||
|
||||
Only `es_DO` is exposed by the component language filter. Weblate can reuse
|
||||
good translations between versions through shared translation memory, but it
|
||||
commits each version to its own branch and catalog.
|
||||
|
||||
## Translator workflow
|
||||
|
||||
1. Sign in to Weblate and open the project for the required Tryton series.
|
||||
2. Select Spanish (Dominican Republic) and the relevant component.
|
||||
3. Translate or improve a string. Preserve placeholders, XML/HTML markup,
|
||||
keyboard accelerators, and plural forms exactly as Weblate indicates.
|
||||
4. Prefer Dominican accounting, tax, commercial, and administrative terms.
|
||||
Use the project glossary for terminology that must be consistent.
|
||||
5. Resolve Weblate checks before marking work complete. Do not suppress a
|
||||
failing check merely to increase the completion percentage.
|
||||
6. Use suggestions when the translator is uncertain; a reviewer can accept or
|
||||
replace them without losing the discussion.
|
||||
|
||||
Weblate groups pending edits and commits them after the configured delay. It
|
||||
then pushes to `translation/weblate-X.Y`. A translation is not released merely
|
||||
because it appears in Weblate; it must pass the Git review flow below.
|
||||
|
||||
## Maintainer promotion workflow
|
||||
|
||||
1. Confirm that the Weblate component has no repository error or lock.
|
||||
2. In Weblate, commit pending changes and push the repository when an immediate
|
||||
release is required. Otherwise allow the automatic commit interval to run.
|
||||
3. Inspect the version's `translation/weblate-X.Y` branch in Gitea.
|
||||
4. Open a pull request to the promotion target from the version mapping table.
|
||||
5. Review terminology and confirm that the diff only contains expected PO
|
||||
catalogs. Reject unrelated generated or source changes.
|
||||
6. Require the PO validation and relevant package tests to pass.
|
||||
7. Merge the pull request. For 8.0, open and review the second pull request from
|
||||
`integration/8.0` to `8.0`.
|
||||
8. Allow the Gitea push webhook to refresh Weblate. If necessary, run
|
||||
**Manage > Repository maintenance > Update** for the affected component.
|
||||
|
||||
Never rebase or force-push a protected release or integration branch. If a
|
||||
Weblate branch must be repaired, first commit or discard pending Weblate work,
|
||||
then fast-forward the branch from its promotion target.
|
||||
|
||||
## Deploying catalog changes
|
||||
|
||||
Build or install the Tryton packages from the reviewed release branch. On each
|
||||
database, activate Spanish (Dominican Republic) as a translatable language and
|
||||
run:
|
||||
|
||||
```console
|
||||
trytond-admin -d DATABASE --all --activate-dependencies
|
||||
```
|
||||
|
||||
Restart the Tryton services after updating the installed package or container.
|
||||
Users can then select Spanish (Dominican Republic) in their preferences.
|
||||
`es_419` does not need to be active and is not a runtime dependency.
|
||||
|
||||
For container deployments, update the image tag or immutable digest only after
|
||||
the new package build succeeds. Back up the database before a production
|
||||
module update and keep the previous image available for rollback.
|
||||
|
||||
## Adding a Tryton series
|
||||
|
||||
1. Create the release branch and `translation/weblate-X.Y` branch from the
|
||||
same reviewed commit.
|
||||
2. Add `es_DO` to the core language data and add an `es_DO.po` catalog beside
|
||||
every maintained core, resource, module, and SAO catalog.
|
||||
3. Create a Weblate project with hooks and shared translation memory enabled.
|
||||
4. Add the server-core and server-resources PO components.
|
||||
5. Add the SAO PO component.
|
||||
6. Add component discovery to server core with this regular expression:
|
||||
|
||||
```text
|
||||
^modules/(?P<component>[^/]+)/locale/(?P<language>es_DO)\.po$
|
||||
```
|
||||
|
||||
7. Restrict languages to `^es_DO$`, disable creation of arbitrary languages,
|
||||
enable push-on-commit, and point push to the dedicated translation branch.
|
||||
8. Verify that the component count equals modules plus core, resources, SAO,
|
||||
and the automatically created glossary.
|
||||
9. Add CI and branch protection before accepting the first translation PR.
|
||||
|
||||
## Webhook and credential model
|
||||
|
||||
The Gitea repository has one active push webhook targeting:
|
||||
|
||||
```text
|
||||
https://translate.tryton.org.do/hooks/gitea/
|
||||
```
|
||||
|
||||
One repository-level webhook covers all translation branches. Weblate uses its
|
||||
own repository credential to fetch and push; Gitea Actions uses the short-lived
|
||||
repository-scoped `GITEA_TOKEN`. Personal passwords and Weblate API tokens must
|
||||
remain in the respective secret stores, never in workflow YAML, URLs, logs, or
|
||||
documentation.
|
||||
|
||||
Rotate a credential immediately if it is exposed. After rotation, test a fetch
|
||||
and a push on a translation branch, then confirm that the webhook still causes
|
||||
Weblate to update.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Weblate shows an outdated source
|
||||
|
||||
Confirm the Gitea webhook delivery, then update the repository from Weblate.
|
||||
Check that the component branch matches the version mapping and that there are
|
||||
no pending changes blocking a pull.
|
||||
|
||||
### Weblate cannot push
|
||||
|
||||
Check the component repository error, repository write permission, and branch
|
||||
name. Weblate must push to `translation/weblate-X.Y`, not to a protected release
|
||||
branch. Do not weaken release-branch protection to work around this error.
|
||||
|
||||
### A component is locked
|
||||
|
||||
Read the recorded repository error first. Correct authentication, connectivity,
|
||||
or a Git conflict, update the repository, and only then unlock it. Repeatedly
|
||||
unlocking without correcting the cause can lose or duplicate work.
|
||||
|
||||
### A new module is missing
|
||||
|
||||
Verify that `modules/<module>/locale/es_DO.po` exists on the translation branch,
|
||||
then run component discovery from the server-core component. A missing catalog
|
||||
must be added in Git before Weblate can discover it.
|
||||
|
||||
### A PO check fails in CI
|
||||
|
||||
Run the repository validator locally:
|
||||
|
||||
```console
|
||||
python -m pip install polib
|
||||
python ci/validate_po.py
|
||||
```
|
||||
|
||||
Fix duplicate messages, invalid syntax, plural forms, or malformed placeholders
|
||||
in Weblate or on the translation branch. Do not bypass the validator.
|
||||
|
||||
## Recovery and audit checklist
|
||||
|
||||
- Export or back up the Weblate database and application data with the normal
|
||||
infrastructure backup process; Git alone does not retain suggestions,
|
||||
comments, permissions, or translation memory.
|
||||
- Verify Weblate backups by restoring them in an isolated environment.
|
||||
- Keep the Gitea repository as the source of truth for released PO files.
|
||||
- Audit Weblate project access and repository credentials periodically.
|
||||
- Check that all four translation branches still exist and are not ahead with
|
||||
unreviewed changes.
|
||||
- Confirm webhook delivery and one repository refresh after maintenance.
|
||||
- Record the pull request and deployed image/package version for every release.
|
||||
|
||||
@@ -355,11 +355,9 @@ class Move(DescriptionOriginMixin, ModelSQL, ModelView):
|
||||
'''
|
||||
pool = Pool()
|
||||
MoveLine = pool.get('account.move.line')
|
||||
Currency = pool.get('currency.currency')
|
||||
line = MoveLine.__table__()
|
||||
transaction = Transaction()
|
||||
cursor = transaction.connection.cursor()
|
||||
cast = Currency.rounding.sql_cast
|
||||
|
||||
for company, moves in groupby(moves, key=lambda m: m.company):
|
||||
currency = company.currency
|
||||
@@ -372,7 +370,7 @@ class Move(DescriptionOriginMixin, ModelSQL, ModelView):
|
||||
group_by=line.move,
|
||||
having=Abs(Round(
|
||||
Sum(line.debit - line.credit),
|
||||
currency.digits)) < cast(abs(currency.rounding)))
|
||||
currency.digits)) < abs(currency.rounding))
|
||||
cursor.execute(*line.update(
|
||||
[line.state],
|
||||
['valid'],
|
||||
@@ -384,7 +382,7 @@ class Move(DescriptionOriginMixin, ModelSQL, ModelView):
|
||||
group_by=line.move,
|
||||
having=Abs(Round(
|
||||
Sum(line.debit - line.credit),
|
||||
currency.digits)) >= cast(abs(currency.rounding)))
|
||||
currency.digits)) >= abs(currency.rounding))
|
||||
cursor.execute(*line.update(
|
||||
[line.state],
|
||||
['draft'],
|
||||
@@ -1446,13 +1444,16 @@ class Line(DescriptionOriginMixin, MoveLineMixin, ModelSQL, ModelView):
|
||||
}])
|
||||
|
||||
@classmethod
|
||||
def on_modification(cls, mode, lines, field_names=None):
|
||||
def check_modification(cls, mode, lines, values=None, external=False):
|
||||
pool = Pool()
|
||||
Move = pool.get('account.move')
|
||||
|
||||
super().on_modification(mode, lines, field_names=field_names)
|
||||
super().check_modification(
|
||||
mode, lines, values=values, external=external)
|
||||
|
||||
if field_names is None or (field_names - cls._check_modify_exclude):
|
||||
if (mode in {'create', 'delete'}
|
||||
or (mode == 'write'
|
||||
and values.keys() - cls._check_modify_exclude)):
|
||||
journal_period_done = set()
|
||||
for line in lines:
|
||||
if line.move.state == 'posted':
|
||||
@@ -1473,8 +1474,7 @@ class Line(DescriptionOriginMixin, MoveLineMixin, ModelSQL, ModelView):
|
||||
'account.msg_delete_line_reconciled',
|
||||
line=line.rec_name))
|
||||
if (mode == 'write'
|
||||
and field_names is not None
|
||||
and field_names & cls._reconciliation_modify_disallow):
|
||||
and values.keys() & cls._reconciliation_modify_disallow):
|
||||
for line in lines:
|
||||
if line.reconciliation:
|
||||
raise AccessError(gettext(
|
||||
|
||||
@@ -767,69 +767,6 @@ class AccountTestCase(
|
||||
]
|
||||
move.save()
|
||||
|
||||
@with_transaction()
|
||||
def test_move_validation(self):
|
||||
"Test move validation"
|
||||
pool = Pool()
|
||||
Party = pool.get('party.party')
|
||||
FiscalYear = pool.get('account.fiscalyear')
|
||||
Journal = pool.get('account.journal')
|
||||
Account = pool.get('account.account')
|
||||
Move = pool.get('account.move')
|
||||
Line = pool.get('account.move.line')
|
||||
|
||||
party = Party(name='Party')
|
||||
party.save()
|
||||
|
||||
company = create_company()
|
||||
with set_company(company):
|
||||
fiscalyear = get_fiscalyear(company)
|
||||
fiscalyear.save()
|
||||
FiscalYear.create_period([fiscalyear])
|
||||
period = fiscalyear.periods[0]
|
||||
create_chart(company)
|
||||
|
||||
journal_revenue, = Journal.search([
|
||||
('code', '=', 'REV'),
|
||||
])
|
||||
revenue, = Account.search([
|
||||
('type.revenue', '=', True),
|
||||
('closed', '=', False),
|
||||
], limit=1)
|
||||
receivable, = Account.search([
|
||||
('type.receivable', '=', True),
|
||||
('closed', '=', False),
|
||||
], limit=1)
|
||||
|
||||
move = Move()
|
||||
move.period = period
|
||||
move.journal = journal_revenue
|
||||
move.date = period.start_date
|
||||
move.lines = [
|
||||
Line(account=revenue, credit=Decimal(100)),
|
||||
]
|
||||
move.save()
|
||||
|
||||
line, = move.lines
|
||||
self.assertEqual(line.state, 'draft')
|
||||
|
||||
move.lines += (
|
||||
Line(account=receivable, debit=Decimal(100), party=party),
|
||||
)
|
||||
move.save()
|
||||
|
||||
self.assertEqual([l.state for l in move.lines], ['valid', 'valid'])
|
||||
|
||||
line = move.lines[-1]
|
||||
line.credit = Decimal(50)
|
||||
line.save()
|
||||
|
||||
self.assertEqual(line.state, 'draft')
|
||||
|
||||
line.credit = Decimal(100)
|
||||
line.save()
|
||||
self.assertEqual(line.state, 'valid')
|
||||
|
||||
@with_transaction()
|
||||
def test_find_best_reconciliation(self):
|
||||
"Test find best reconciliation"
|
||||
|
||||
@@ -32,7 +32,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<CreDtTm>${datetime.datetime.now().replace(microsecond=0).isoformat()}</CreDtTm>
|
||||
<!-- Authstn -->
|
||||
<NbOfTxs>${sum(len(payments) for _, payments in group.sepa_payments)}</NbOfTxs>
|
||||
<CtrlSum>${format(sum(p.amount for p in group.payments), 'f')}</CtrlSum>
|
||||
<CtrlSum>${format(sum(p.amount for p in group.payments).normalize(), 'f')}</CtrlSum>
|
||||
<!-- PmtTpInf -->
|
||||
<!-- ReqdColltnDt -->
|
||||
<InitgPty>
|
||||
@@ -45,7 +45,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<PmtMtd>TRF</PmtMtd>
|
||||
<BtchBookg>${'true' if group.journal.sepa_batch_booking else 'false'}</BtchBookg>
|
||||
<NbOfTxs>${len(payments)}</NbOfTxs>
|
||||
<CtrlSum>${format(sum(p.amount for p in payments), 'f')}</CtrlSum>
|
||||
<CtrlSum>${format(sum(p.amount for p in payments).normalize(), 'f')}</CtrlSum>
|
||||
<PmtTpInf>
|
||||
<!-- InstrPrty -->
|
||||
<SvcLvl>
|
||||
@@ -77,7 +77,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
</PmtId>
|
||||
<!-- PmtTpInf --> <!-- EPC only at payment information level -->
|
||||
<Amt>
|
||||
<InstdAmt py:attrs="{'Ccy': payment.currency.code}">${format(payment.amount, 'f')}</InstdAmt>
|
||||
<InstdAmt py:attrs="{'Ccy': payment.currency.code}">${format(payment.amount.normalize(), 'f')}</InstdAmt>
|
||||
<!-- EqvtAmt -->
|
||||
</Amt>
|
||||
<!-- XchgRateInf -->
|
||||
|
||||
@@ -13,7 +13,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<CreDtTm>${datetime.datetime.now().replace(microsecond=0).isoformat()}</CreDtTm>
|
||||
<!-- Authstn -->
|
||||
<NbOfTxs>${sum(len(payments) for _, payments in group.sepa_payments)}</NbOfTxs>
|
||||
<CtrlSum>${format(sum(p.amount for p in group.payments), 'f')}</CtrlSum>
|
||||
<CtrlSum>${format(sum(p.amount for p in group.payments).normalize(), 'f')}</CtrlSum>
|
||||
<!-- PmtTpInf -->
|
||||
<!-- ReqdColltnDt -->
|
||||
<InitgPty>
|
||||
@@ -26,7 +26,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<PmtMtd>TRF</PmtMtd>
|
||||
<BtchBookg>${'true' if group.journal.sepa_batch_booking else 'false'}</BtchBookg>
|
||||
<NbOfTxs>${len(payments)}</NbOfTxs>
|
||||
<CtrlSum>${format(sum(p.amount for p in payments), 'f')}</CtrlSum>
|
||||
<CtrlSum>${format(sum(p.amount for p in payments).normalize(), 'f')}</CtrlSum>
|
||||
<PmtTpInf>
|
||||
<!-- InstrPrty -->
|
||||
<SvcLvl>
|
||||
@@ -59,7 +59,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
</PmtId>
|
||||
<!-- PmtTpInf --> <!-- EPC only at payment information level -->
|
||||
<Amt>
|
||||
<InstdAmt py:attrs="{'Ccy': payment.currency.code}">${format(payment.amount, 'f')}</InstdAmt>
|
||||
<InstdAmt py:attrs="{'Ccy': payment.currency.code}">${format(payment.amount.normalize(), 'f')}</InstdAmt>
|
||||
<!-- EqvtAmt -->
|
||||
</Amt>
|
||||
<!-- XchgRateInf -->
|
||||
|
||||
@@ -15,7 +15,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<MsgId>${group.sepa_message_id[:35]}</MsgId>
|
||||
<CreDtTm>${datetime.datetime.now().replace(microsecond=0).isoformat()}</CreDtTm>
|
||||
<NbOfTxs>${sum(len(payments) for _, payments in group.sepa_payments)}</NbOfTxs>
|
||||
<CtrlSum>${format(sum(p.amount for p in group.payments), 'f')}</CtrlSum>
|
||||
<CtrlSum>${format(sum(p.amount for p in group.payments).normalize(), 'f')}</CtrlSum>
|
||||
<InitgPty>
|
||||
${PartyIdentification(group.sepa_initiating_party, id=group.journal.sepa_payable_initiator_id, with_address=False)}
|
||||
</InitgPty>
|
||||
@@ -25,7 +25,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<PmtMtd>TRF</PmtMtd>
|
||||
<BtchBookg>${'true' if group.journal.sepa_batch_booking else 'false'}</BtchBookg>
|
||||
<NbOfTxs>${len(payments)}</NbOfTxs>
|
||||
<CtrlSum>${format(sum(p.amount for p in payments), 'f')}</CtrlSum>
|
||||
<CtrlSum>${format(sum(p.amount for p in payments).normalize(), 'f')}</CtrlSum>
|
||||
<PmtTpInf>
|
||||
<!-- InstrPrty -->
|
||||
<SvcLvl>
|
||||
@@ -52,7 +52,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
</PmtId>
|
||||
<!-- PmtTpInf --> <!-- EPC only at payment information level -->
|
||||
<Amt>
|
||||
<InstdAmt py:attrs="{'Ccy': payment.currency.code}">${format(payment.amount, 'f')}</InstdAmt>
|
||||
<InstdAmt py:attrs="{'Ccy': payment.currency.code}">${format(payment.amount.normalize(), 'f')}</InstdAmt>
|
||||
</Amt>
|
||||
<!-- ChrgBr --> <!-- EPC only at payment information level -->
|
||||
<!-- UltmtDbtr -->
|
||||
|
||||
@@ -32,7 +32,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<CreDtTm>${datetime.datetime.now().replace(microsecond=0).isoformat()}</CreDtTm>
|
||||
<!-- Authstn -->
|
||||
<NbOfTxs>${sum(len(payments) for _, payments in group.sepa_payments)}</NbOfTxs>
|
||||
<CtrlSum>${format(sum(p.amount for p in group.payments), 'f')}</CtrlSum>
|
||||
<CtrlSum>${format(sum(p.amount for p in group.payments).normalize(), 'f')}</CtrlSum>
|
||||
<!-- PmtTpInf -->
|
||||
<!-- ReqdColltnDt -->
|
||||
<InitgPty>
|
||||
@@ -45,7 +45,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<PmtMtd>DD</PmtMtd>
|
||||
<BtchBookg>${'true' if group.journal.sepa_batch_booking else 'false'}</BtchBookg>
|
||||
<NbOfTxs>${len(payments)}</NbOfTxs>
|
||||
<CtrlSum>${format(sum(p.amount for p in payments), 'f')}</CtrlSum>
|
||||
<CtrlSum>${format(sum(p.amount for p in payments).normalize(), 'f')}</CtrlSum>
|
||||
<PmtTpInf>
|
||||
<!-- InstrPrty -->
|
||||
<SvcLvl>
|
||||
@@ -83,7 +83,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<EndToEndId>${payment.sepa_end_to_end_id}</EndToEndId>
|
||||
</PmtId>
|
||||
<!-- PmtTpInf -->
|
||||
<InstdAmt py:attrs="{'Ccy': payment.currency.code}">${format(payment.amount, 'f')}</InstdAmt>
|
||||
<InstdAmt py:attrs="{'Ccy': payment.currency.code}">${format(payment.amount.normalize(), 'f')}</InstdAmt>
|
||||
<!-- ChrgBr --> <!-- EPC only at payment information level -->
|
||||
<DrctDbtTx>
|
||||
<MndtRltdInf py:with="mandate = payment.sepa_mandate">
|
||||
|
||||
@@ -13,7 +13,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<CreDtTm>${datetime.datetime.now().replace(microsecond=0).isoformat()}</CreDtTm>
|
||||
<!-- Authstn -->
|
||||
<NbOfTxs>${len(group.payments)}</NbOfTxs>
|
||||
<CtrlSum>${format(sum(p.amount for p in group.payments), 'f')}</CtrlSum>
|
||||
<CtrlSum>${format(sum(p.amount for p in group.payments).normalize(), 'f')}</CtrlSum>
|
||||
<!-- PmtTpInf -->
|
||||
<!-- ReqdColltnDt -->
|
||||
<InitgPty>
|
||||
@@ -26,7 +26,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<PmtMtd>DD</PmtMtd>
|
||||
<BtchBookg>${'true' if group.journal.sepa_batch_booking else 'false'}</BtchBookg>
|
||||
<NbOfTxs>${len(payments)}</NbOfTxs>
|
||||
<CtrlSum>${format(sum(p.amount for p in payments), 'f')}</CtrlSum>
|
||||
<CtrlSum>${format(sum(p.amount for p in payments).normalize(), 'f')}</CtrlSum>
|
||||
<PmtTpInf>
|
||||
<!-- InstrPrty -->
|
||||
<SvcLvl>
|
||||
@@ -64,7 +64,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<EndToEndId>${payment.sepa_end_to_end_id}</EndToEndId>
|
||||
</PmtId>
|
||||
<!-- PmtTpInf -->
|
||||
<InstdAmt py:attrs="{'Ccy': payment.currency.code}">${format(payment.amount, 'f')}</InstdAmt>
|
||||
<InstdAmt py:attrs="{'Ccy': payment.currency.code}">${format(payment.amount.normalize(), 'f')}</InstdAmt>
|
||||
<!-- ChrgBr --> <!-- EPC only at payment information level -->
|
||||
<DrctDbtTx>
|
||||
<MndtRltdInf py:with="mandate = payment.sepa_mandate">
|
||||
|
||||
@@ -15,7 +15,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<MsgId>${group.sepa_message_id[:35]}</MsgId>
|
||||
<CreDtTm>${datetime.datetime.now().replace(microsecond=0).isoformat()}</CreDtTm>
|
||||
<NbOfTxs>${sum(len(payments) for _, payments in group.sepa_payments)}</NbOfTxs>
|
||||
<CtrlSum>${format(sum(p.amount for p in group.payments), 'f')}</CtrlSum>
|
||||
<CtrlSum>${format(sum(p.amount for p in group.payments).normalize(), 'f')}</CtrlSum>
|
||||
<InitgPty>
|
||||
${PartyIdentification(group.sepa_initiating_party, id=group.journal.sepa_receivable_initiator_id, with_address=False)}
|
||||
</InitgPty>
|
||||
@@ -25,7 +25,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<PmtMtd>DD</PmtMtd>
|
||||
<BtchBookg>${'true' if group.journal.sepa_batch_booking else 'false'}</BtchBookg>
|
||||
<NbOfTxs>${len(payments)}</NbOfTxs>
|
||||
<CtrlSum>${format(sum(p.amount for p in payments), 'f')}</CtrlSum>
|
||||
<CtrlSum>${format(sum(p.amount for p in payments).normalize(), 'f')}</CtrlSum>
|
||||
<PmtTpInf>
|
||||
<SvcLvl>
|
||||
<Cd>SEPA</Cd>
|
||||
@@ -56,7 +56,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<InstrId>${payment.sepa_instruction_id}</InstrId>
|
||||
<EndToEndId>${payment.sepa_end_to_end_id}</EndToEndId>
|
||||
</PmtId>
|
||||
<InstdAmt py:attrs="{'Ccy': payment.currency.code}">${format(payment.amount, 'f')}</InstdAmt>
|
||||
<InstdAmt py:attrs="{'Ccy': payment.currency.code}">${format(payment.amount.normalize(), 'f')}</InstdAmt>
|
||||
<!-- ChrgBr --> <!-- EPC only at payment information level -->
|
||||
<DrctDbtTx>
|
||||
<MndtRltdInf py:with="mandate = payment.sepa_mandate">
|
||||
|
||||
@@ -26,7 +26,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<MsgId>${group.sepa_message_id[:35]}</MsgId>
|
||||
<CreDtTm>${datetime.datetime.now().replace(microsecond=0).isoformat()}</CreDtTm>
|
||||
<NbOfTxs>${sum(len(payments) for _, payments in group.sepa_payments)}</NbOfTxs>
|
||||
<CtrlSum>${format(sum(p.amount for p in group.payments), 'f')}</CtrlSum>
|
||||
<CtrlSum>${format(sum(p.amount for p in group.payments).normalize(), 'f')}</CtrlSum>
|
||||
<InitgPty>
|
||||
${PartyIdentification(group.sepa_initiating_party)}
|
||||
</InitgPty>
|
||||
@@ -36,7 +36,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<PmtMtd>TRF</PmtMtd>
|
||||
<BtchBookg>${'true' if group.journal.sepa_batch_booking else 'false'}</BtchBookg>
|
||||
<NbOfTxs>${len(payments)}</NbOfTxs>
|
||||
<CtrlSum>${format(sum(p.amount for p in payments), 'f')}</CtrlSum>
|
||||
<CtrlSum>${format(sum(p.amount for p in payments).normalize(), 'f')}</CtrlSum>
|
||||
<PmtTpInf>
|
||||
<!-- InstrPrty -->
|
||||
<SvcLvl>
|
||||
@@ -62,7 +62,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<EndToEndId>${payment.sepa_end_to_end_id}</EndToEndId>
|
||||
</PmtId>
|
||||
<Amt>
|
||||
<InstdAmt py:attrs="{'Ccy': payment.currency.code}">${format(payment.amount, 'f')}</InstdAmt>
|
||||
<InstdAmt py:attrs="{'Ccy': payment.currency.code}">${format(payment.amount.normalize(), 'f')}</InstdAmt>
|
||||
<!-- EqvtAmt -->
|
||||
</Amt>
|
||||
<!-- ChrgBr --> <!-- EPC only at payment information level -->
|
||||
|
||||
@@ -32,7 +32,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<MsgId>${group.sepa_message_id[:35]}</MsgId>
|
||||
<CreDtTm>${datetime.datetime.now().replace(microsecond=0).isoformat()}</CreDtTm>
|
||||
<NbOfTxs>${sum(len(payments) for _, payments in group.sepa_payments)}</NbOfTxs>
|
||||
<CtrlSum>${format(sum(p.amount for p in group.payments), 'f')}</CtrlSum>
|
||||
<CtrlSum>${format(sum(p.amount for p in group.payments).normalize(), 'f')}</CtrlSum>
|
||||
<InitgPty>
|
||||
${PartyIdentification(group.sepa_initiating_party)}
|
||||
</InitgPty>
|
||||
@@ -42,7 +42,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<PmtMtd>DD</PmtMtd>
|
||||
<BtchBookg>${'true' if group.journal.sepa_batch_booking else 'false'}</BtchBookg>
|
||||
<NbOfTxs>${len(payments)}</NbOfTxs>
|
||||
<CtrlSum>${format(sum(p.amount for p in payments), 'f')}</CtrlSum>
|
||||
<CtrlSum>${format(sum(p.amount for p in payments).normalize(), 'f')}</CtrlSum>
|
||||
<PmtTpInf>
|
||||
<SvcLvl>
|
||||
<Cd>SEPA</Cd>
|
||||
@@ -73,7 +73,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<InstrId>${payment.sepa_instruction_id}</InstrId>
|
||||
<EndToEndId>${payment.sepa_end_to_end_id}</EndToEndId>
|
||||
</PmtId>
|
||||
<InstdAmt py:attrs="{'Ccy': payment.currency.code}">${format(payment.amount, 'f')}</InstdAmt>
|
||||
<InstdAmt py:attrs="{'Ccy': payment.currency.code}">${format(payment.amount.normalize(), 'f')}</InstdAmt>
|
||||
<DrctDbtTx>
|
||||
<MndtRltdInf py:with="mandate = payment.sepa_mandate">
|
||||
<MndtId>${mandate.identification}</MndtId>
|
||||
|
||||
@@ -90,7 +90,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<py:otherwise>
|
||||
<cac:PaymentTerms py:for="line in this.invoice.lines_to_pay">
|
||||
<cbc:Note py:if="this.invoice.payment_term and this.invoice.payment_term.description">${this.invoice.payment_term.description}</cbc:Note>
|
||||
<cbc:Amount py:attrs="{'currencyID': this.invoice.currency.code}">${format(line.amount, 'f')}</cbc:Amount>
|
||||
<cbc:Amount py:attrs="{'currencyID': this.invoice.currency.code}">${format(line.amount.normalize(), 'f')}</cbc:Amount>
|
||||
<cbc:PaymentDueDate>${line.maturity_date}</cbc:PaymentDueDate>
|
||||
</cac:PaymentTerms>
|
||||
</py:otherwise>
|
||||
@@ -101,16 +101,16 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<cbc:AllowanceChargeReasonCode py:if="reason_code">${reason_code}</cbc:AllowanceChargeReasonCode>
|
||||
</py:with>
|
||||
<cbc:AllowanceChargeReason>${allowance_charge.product.name}</cbc:AllowanceChargeReason>
|
||||
<cbc:Amount py:attrs="{'currencyID': this.invoice.currency.code}">${format(abs(allowance_charge.amount), 'f')}</cbc:Amount>
|
||||
<cbc:Amount py:attrs="{'currencyID': this.invoice.currency.code}">${format(abs(allowance_charge.amount).normalize(), 'f')}</cbc:Amount>
|
||||
<cac:TaxCategory py:for="tax_line in allowance_charge.invoice_taxes">
|
||||
${TaxCategory(tax_line.tax)}
|
||||
</cac:TaxCategory>
|
||||
</cac:AllowanceCharge>
|
||||
<cac:TaxTotal py:for="group, lines, amount in this.taxes">
|
||||
<cbc:TaxAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(-amount, 'f')}</cbc:TaxAmount>
|
||||
<cbc:TaxAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format((-amount).normalize(), 'f')}</cbc:TaxAmount>
|
||||
<cac:TaxSubtotal py:for="line in lines">
|
||||
<cbc:TaxableAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(-line.base, 'f')}</cbc:TaxableAmount>
|
||||
<cbc:TaxAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(-line.amount, 'f')}</cbc:TaxAmount>
|
||||
<cbc:TaxableAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format((-line.base).normalize(), 'f')}</cbc:TaxableAmount>
|
||||
<cbc:TaxAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format((-line.amount).normalize(), 'f')}</cbc:TaxAmount>
|
||||
<cbc:Percent py:if="line.tax.type == 'percentage' and not (specification or '').startswith('peppol')">${format((line.tax.rate * 100).normalize(), 'f')}</cbc:Percent>
|
||||
<cac:TaxCategory>
|
||||
${TaxCategory(line.tax)}
|
||||
@@ -118,19 +118,19 @@ this repository contains the full copyright notices and license terms. -->
|
||||
</cac:TaxSubtotal>
|
||||
</cac:TaxTotal>
|
||||
<cac:LegalMonetaryTotal>
|
||||
<cbc:LineExtensionAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(-this.invoice.untaxed_amount, 'f')}</cbc:LineExtensionAmount>
|
||||
<cbc:TaxExclusiveAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(-this.invoice.untaxed_amount, 'f')}</cbc:TaxExclusiveAmount>
|
||||
<cbc:TaxInclusiveAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(-(this.invoice.untaxed_amount + this.invoice.tax_amount), 'f')}</cbc:TaxInclusiveAmount>
|
||||
<cbc:LineExtensionAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format((-this.invoice.untaxed_amount).normalize(), 'f')}</cbc:LineExtensionAmount>
|
||||
<cbc:TaxExclusiveAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format((-this.invoice.untaxed_amount).normalize(), 'f')}</cbc:TaxExclusiveAmount>
|
||||
<cbc:TaxInclusiveAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format((-(this.invoice.untaxed_amount + this.invoice.tax_amount)).normalize(), 'f')}</cbc:TaxInclusiveAmount>
|
||||
<py:if test="this.invoice.total_amount != (this.invoice.untaxed_amount + this.invoice.tax_amount)">
|
||||
<cbc:PayableRoundingAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(-(this.invoice.total_amount - this.invoice.untaxed_amount - this.invoice.tax_amount), 'f')}</cbc:PayableRoundingAmount>
|
||||
<cbc:PayableRoundingAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format((-(this.invoice.total_amount - this.invoice.untaxed_amount - this.invoice.tax_amount)).normalize(), 'f')}</cbc:PayableRoundingAmount>
|
||||
</py:if>
|
||||
<cbc:PrepaidAmount py:if="this.prepaid_amount" py:attrs="{'currencyID': this.invoice.currency.code}">${format(-this.prepaid_amount, 'f')}</cbc:PrepaidAmount>
|
||||
<cbc:PayableAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(-this.invoice.total_amount + this.prepaid_amount, 'f')}</cbc:PayableAmount>
|
||||
<cbc:PrepaidAmount py:if="this.prepaid_amount" py:attrs="{'currencyID': this.invoice.currency.code}">${format((-this.prepaid_amount).normalize(), 'f')}</cbc:PrepaidAmount>
|
||||
<cbc:PayableAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format((-this.invoice.total_amount + this.prepaid_amount).normalize(), 'f')}</cbc:PayableAmount>
|
||||
</cac:LegalMonetaryTotal>
|
||||
<cac:CreditNoteLine py:for="line_id, line in enumerate(this.lines, 1)">
|
||||
<cbc:ID>${line_id}</cbc:ID>
|
||||
<cbc:CreditedQuantity py:attrs="{'unitCode': line.unit.unece_code if line.unit and line.unit.unece_code else 'ZZ'}">${format(-Decimal(str(line.quantity)).normalize(), 'f')}</cbc:CreditedQuantity>
|
||||
<cbc:LineExtensionAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(-line.amount, 'f')}</cbc:LineExtensionAmount>
|
||||
<cbc:CreditedQuantity py:attrs="{'unitCode': line.unit.unece_code if line.unit and line.unit.unece_code else 'ZZ'}">${format(-Decimal(str(line.quantity)), 'f')}</cbc:CreditedQuantity>
|
||||
<cbc:LineExtensionAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format((-line.amount).normalize(), 'f')}</cbc:LineExtensionAmount>
|
||||
<cac:Item>
|
||||
<cbc:Description py:if="line.description">${line.description}</cbc:Description>
|
||||
<cbc:Name>${line.product.name if line.product else line.summary}</cbc:Name>
|
||||
|
||||
@@ -90,7 +90,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<py:otherwise>
|
||||
<cac:PaymentTerms py:for="line in this.invoice.lines_to_pay">
|
||||
<cbc:Note py:if="this.invoice.payment_term and this.invoice.payment_term.description">${this.invoice.payment_term.description}</cbc:Note>
|
||||
<cbc:Amount py:attrs="{'currencyID': this.invoice.currency.code}">${format(line.amount, 'f')}</cbc:Amount>
|
||||
<cbc:Amount py:attrs="{'currencyID': this.invoice.currency.code}">${format(line.amount.normalize(), 'f')}</cbc:Amount>
|
||||
<cbc:PaymentDueDate>${line.maturity_date}</cbc:PaymentDueDate>
|
||||
</cac:PaymentTerms>
|
||||
</py:otherwise>
|
||||
@@ -101,16 +101,16 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<cbc:AllowanceChargeReasonCode py:if="reason_code">${reason_code}</cbc:AllowanceChargeReasonCode>
|
||||
</py:with>
|
||||
<cbc:AllowanceChargeReason>${allowance_charge.product.name}</cbc:AllowanceChargeReason>
|
||||
<cbc:Amount py:attrs="{'currencyID': this.invoice.currency.code}">${format(abs(allowance_charge.amount), 'f')}</cbc:Amount>
|
||||
<cbc:Amount py:attrs="{'currencyID': this.invoice.currency.code}">${format(abs(allowance_charge.amount).normalize(), 'f')}</cbc:Amount>
|
||||
<cac:TaxCategory py:for="tax_line in allowance_charge.invoice_taxes">
|
||||
${TaxCategory(tax_line.tax)}
|
||||
</cac:TaxCategory>
|
||||
</cac:AllowanceCharge>
|
||||
<cac:TaxTotal py:for="group, lines, amount in this.taxes">
|
||||
<cbc:TaxAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(amount, 'f')}</cbc:TaxAmount>
|
||||
<cbc:TaxAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(amount.normalize(), 'f')}</cbc:TaxAmount>
|
||||
<cac:TaxSubtotal py:for="line in lines">
|
||||
<cbc:TaxableAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(line.base, 'f')}</cbc:TaxableAmount>
|
||||
<cbc:TaxAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(line.amount, 'f')}</cbc:TaxAmount>
|
||||
<cbc:TaxableAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(line.base.normalize(), 'f')}</cbc:TaxableAmount>
|
||||
<cbc:TaxAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(line.amount.normalize(), 'f')}</cbc:TaxAmount>
|
||||
<cbc:Percent py:if="line.tax.type == 'percentage' and not (specification or '').startswith('peppol')">${format((line.tax.rate * 100).normalize(), 'f')}</cbc:Percent>
|
||||
<cac:TaxCategory>
|
||||
${TaxCategory(line.tax)}
|
||||
@@ -118,19 +118,19 @@ this repository contains the full copyright notices and license terms. -->
|
||||
</cac:TaxSubtotal>
|
||||
</cac:TaxTotal>
|
||||
<cac:LegalMonetaryTotal>
|
||||
<cbc:LineExtensionAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(this.invoice.untaxed_amount, 'f')}</cbc:LineExtensionAmount>
|
||||
<cbc:TaxExclusiveAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(this.invoice.untaxed_amount, 'f')}</cbc:TaxExclusiveAmount>
|
||||
<cbc:TaxInclusiveAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format((this.invoice.untaxed_amount + this.invoice.tax_amount), 'f')}</cbc:TaxInclusiveAmount>
|
||||
<cbc:LineExtensionAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(this.invoice.untaxed_amount.normalize(), 'f')}</cbc:LineExtensionAmount>
|
||||
<cbc:TaxExclusiveAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(this.invoice.untaxed_amount.normalize(), 'f')}</cbc:TaxExclusiveAmount>
|
||||
<cbc:TaxInclusiveAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format((this.invoice.untaxed_amount + this.invoice.tax_amount).normalize(), 'f')}</cbc:TaxInclusiveAmount>
|
||||
<py:if test="this.invoice.total_amount != (this.invoice.untaxed_amount + this.invoice.tax_amount)">
|
||||
<cbc:PayableRoundingAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format((this.invoice.total_amount - this.invoice.untaxed_amount - this.invoice.tax_amount), 'f')}</cbc:PayableRoundingAmount>
|
||||
<cbc:PayableRoundingAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format((this.invoice.total_amount - this.invoice.untaxed_amount - this.invoice.tax_amount).normalize(), 'f')}</cbc:PayableRoundingAmount>
|
||||
</py:if>
|
||||
<cbc:PrepaidAmount py:if="this.prepaid_amount" py:attrs="{'currencyID': this.invoice.currency.code}">${format(this.prepaid_amount, 'f')}</cbc:PrepaidAmount>
|
||||
<cbc:PayableAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(this.invoice.total_amount - this.prepaid_amount, 'f')}</cbc:PayableAmount>
|
||||
<cbc:PrepaidAmount py:if="this.prepaid_amount" py:attrs="{'currencyID': this.invoice.currency.code}">${format(this.prepaid_amount.normalize(), 'f')}</cbc:PrepaidAmount>
|
||||
<cbc:PayableAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format((this.invoice.total_amount - this.prepaid_amount).normalize(), 'f')}</cbc:PayableAmount>
|
||||
</cac:LegalMonetaryTotal>
|
||||
<cac:InvoiceLine py:for="line_id, line in enumerate(this.lines, 1)">
|
||||
<cbc:ID>${line_id}</cbc:ID>
|
||||
<cbc:InvoicedQuantity py:attrs="{'unitCode': line.unit.unece_code if line.unit and line.unit.unece_code else 'ZZ'}">${format(Decimal(str(line.quantity)).normalize(), 'f')}</cbc:InvoicedQuantity>
|
||||
<cbc:LineExtensionAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(line.amount, 'f')}</cbc:LineExtensionAmount>
|
||||
<cbc:InvoicedQuantity py:attrs="{'unitCode': line.unit.unece_code if line.unit and line.unit.unece_code else 'ZZ'}">${format(Decimal(str(line.quantity)), 'f')}</cbc:InvoicedQuantity>
|
||||
<cbc:LineExtensionAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(line.amount.normalize(), 'f')}</cbc:LineExtensionAmount>
|
||||
<cac:Item>
|
||||
<cbc:Description py:if="line.description">${line.description}</cbc:Description>
|
||||
<cbc:Name>${line.product.name if line.product else line.summary}</cbc:Name>
|
||||
|
||||
@@ -44,11 +44,11 @@ this repository contains the full copyright notices and license terms. -->
|
||||
</py:def>
|
||||
<py:def function="TradeTax(tax, amount=None, base=None)">
|
||||
<ram:ApplicableTradeTax>
|
||||
<ram:CalculatedAmount py:if="amount" py:attrs="{'currencyID': this.invoice.currency.code}">${format(amount * this.type_sign, 'f')}</ram:CalculatedAmount>
|
||||
<ram:CalculatedAmount py:if="amount" py:attrs="{'currencyID': this.invoice.currency.code}">${format((amount * this.type_sign).normalize(), 'f')}</ram:CalculatedAmount>
|
||||
<ram:TypeCode py:if="tax.unece_code">${tax.unece_code}</ram:TypeCode>
|
||||
<ram:ExemptionReasonCode py:if="hasattr(tax, 'vatex_code') and tax.vatex_code">${tax.vatex_code}</ram:ExemptionReasonCode>
|
||||
<ram:ExemptionReason py:if="tax.legal_notice">${tax.legal_notice}</ram:ExemptionReason>
|
||||
<ram:BasisAmount py:if="base">${format(base * this.type_sign, 'f')}</ram:BasisAmount>
|
||||
<ram:BasisAmount py:if="base">${format((base * this.type_sign).normalize(), 'f')}</ram:BasisAmount>
|
||||
<ram:CategoryCode py:if="tax.unece_category_code">${tax.unece_category_code}</ram:CategoryCode>
|
||||
<ram:RateApplicablePercent py:if="tax.type == 'percentage'">${format((tax.rate * 100).normalize(), 'f')}</ram:RateApplicablePercent>
|
||||
</ram:ApplicableTradeTax>
|
||||
@@ -74,18 +74,18 @@ this repository contains the full copyright notices and license terms. -->
|
||||
</ram:SpecifiedTradeProduct>
|
||||
<ram:SpecifiedLineTradeAgreement>
|
||||
<ram:NetPriceProductTradePrice>
|
||||
<ram:ChargeAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(this.invoice.currency.round(line.unit_price), 'f')}</ram:ChargeAmount>
|
||||
<ram:ChargeAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(this.invoice.currency.round(line.unit_price).normalize(), 'f')}</ram:ChargeAmount>
|
||||
</ram:NetPriceProductTradePrice>
|
||||
</ram:SpecifiedLineTradeAgreement>
|
||||
<ram:SpecifiedLineTradeDelivery>
|
||||
<ram:BilledQuantity py:attrs="{'unitCode': line.unit.unece_code} if line.unit and line.unit.unece_code else {}">${format(abs(Decimal(str(line.quantity)).normalize()), 'f')}</ram:BilledQuantity>
|
||||
<ram:BilledQuantity py:attrs="{'unitCode': line.unit.unece_code} if line.unit and line.unit.unece_code else {}">${format(abs(Decimal(str(line.quantity))), 'f')}</ram:BilledQuantity>
|
||||
</ram:SpecifiedLineTradeDelivery>
|
||||
<ram:SpecifiedLineTradeSettlement>
|
||||
<py:for each="tax in line.invoice_taxes">
|
||||
${TradeTax(tax.tax)}
|
||||
</py:for>
|
||||
<ram:SpecifiedTradeSettlementLineMonetarySummation>
|
||||
<ram:LineTotalAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(line.amount * this.type_sign, 'f')}</ram:LineTotalAmount>
|
||||
<ram:LineTotalAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format((line.amount * this.type_sign).normalize(), 'f')}</ram:LineTotalAmount>
|
||||
</ram:SpecifiedTradeSettlementLineMonetarySummation>
|
||||
</ram:SpecifiedLineTradeSettlement>
|
||||
</ram:IncludedSupplyChainTradeLineItem>
|
||||
@@ -158,14 +158,14 @@ this repository contains the full copyright notices and license terms. -->
|
||||
<ram:DueDateDateTime>
|
||||
${DateTime(line.maturity_date)}
|
||||
</ram:DueDateDateTime>
|
||||
<ram:PartialPaymentAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format((line.amount_second_currency or (line.debit - line.credit)) * this.type_sign, 'f')}</ram:PartialPaymentAmount>
|
||||
<ram:PartialPaymentAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(((line.amount_second_currency or (line.debit - line.credit)) * this.type_sign).normalize(), 'f')}</ram:PartialPaymentAmount>
|
||||
</ram:SpecifiedTradePaymentTerms>
|
||||
<ram:SpecifiedTradeSettlementHeaderMonetarySummation>
|
||||
<ram:LineTotalAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(this.invoice.untaxed_amount * this.type_sign, 'f')}</ram:LineTotalAmount>
|
||||
<ram:TaxBasisTotalAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(this.invoice.untaxed_amount * this.type_sign, 'f')}</ram:TaxBasisTotalAmount>
|
||||
<ram:TaxTotalAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(this.invoice.tax_amount * this.type_sign, 'f')}</ram:TaxTotalAmount>
|
||||
<ram:GrandTotalAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(this.invoice.total_amount * this.type_sign, 'f')}</ram:GrandTotalAmount>
|
||||
<ram:DuePayableAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format(this.invoice.amount_to_pay * this.type_sign, 'f')}</ram:DuePayableAmount>
|
||||
<ram:LineTotalAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format((this.invoice.untaxed_amount * this.type_sign).normalize(), 'f')}</ram:LineTotalAmount>
|
||||
<ram:TaxBasisTotalAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format((this.invoice.untaxed_amount * this.type_sign).normalize(), 'f')}</ram:TaxBasisTotalAmount>
|
||||
<ram:TaxTotalAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format((this.invoice.tax_amount * this.type_sign).normalize(), 'f')}</ram:TaxTotalAmount>
|
||||
<ram:GrandTotalAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format((this.invoice.total_amount * this.type_sign).normalize(), 'f')}</ram:GrandTotalAmount>
|
||||
<ram:DuePayableAmount py:attrs="{'currencyID': this.invoice.currency.code}">${format((this.invoice.amount_to_pay * this.type_sign).normalize(), 'f')}</ram:DuePayableAmount>
|
||||
</ram:SpecifiedTradeSettlementHeaderMonetarySummation>
|
||||
</ram:ApplicableHeaderTradeSettlement>
|
||||
</rsm:SupplyChainTradeTransaction>
|
||||
|
||||
+31
-40
@@ -395,15 +395,12 @@ class Party(
|
||||
else:
|
||||
for country in eu_vat.guess_country(text):
|
||||
if 'eu_vat' in identifier_types:
|
||||
code = country + text
|
||||
if eu_vat.is_valid(code):
|
||||
code = eu_vat.compact(code)
|
||||
add('eu_vat', code)
|
||||
code = eu_vat.compact(country + text)
|
||||
add('eu_vat', code)
|
||||
elif f'{country}_vat' in identifier_types:
|
||||
vat = get_cc_module(country, 'vat')
|
||||
if vat.is_valid(text):
|
||||
code = vat.compact(text)
|
||||
add(f'{country}_vat', code)
|
||||
code = vat.compact(text)
|
||||
add(f'{country}_vat', code)
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
@@ -916,20 +913,18 @@ class Identifier(sequence_ordered(), DeactivableMixin, ModelSQL, ModelView):
|
||||
@fields.depends('type', 'code')
|
||||
def on_change_with_code(self):
|
||||
code = self.code
|
||||
if code and self.type and '_' in self.type:
|
||||
if self.type and '_' in self.type:
|
||||
if module := get_cc_module(*self.type.split('_', 1)):
|
||||
if (hasattr(module, 'is_valid')
|
||||
and module.is_valid(code)):
|
||||
if hasattr(module, 'compact'):
|
||||
try:
|
||||
code = module.compact(code)
|
||||
except stdnum.exceptions.ValidationError:
|
||||
pass
|
||||
if hasattr(module, 'format'):
|
||||
try:
|
||||
code = module.format(code)
|
||||
except stdnum.exceptions.ValidationError:
|
||||
pass
|
||||
if hasattr(module, 'compact'):
|
||||
try:
|
||||
code = module.compact(code)
|
||||
except stdnum.exceptions.ValidationError:
|
||||
pass
|
||||
if hasattr(module, 'format'):
|
||||
try:
|
||||
code = module.format(code)
|
||||
except stdnum.exceptions.ValidationError:
|
||||
pass
|
||||
return code
|
||||
|
||||
def pre_validate(self):
|
||||
@@ -960,18 +955,16 @@ class Identifier(sequence_ordered(), DeactivableMixin, ModelSQL, ModelView):
|
||||
if ((type := values.get('type')) and '_' in type
|
||||
and (code := values.get('code'))):
|
||||
if module := get_cc_module(*type.split('_', 1)):
|
||||
if (hasattr(module, 'is_valid')
|
||||
and module.is_valid(code)):
|
||||
if hasattr(module, 'format'):
|
||||
try:
|
||||
values['code'] = module.format(code)
|
||||
except stdnum.exceptions.ValidationError:
|
||||
pass
|
||||
if hasattr(module, 'compact'):
|
||||
try:
|
||||
values['code_compact'] = module.compact(code)
|
||||
except stdnum.exceptions.ValidationError:
|
||||
pass
|
||||
if hasattr(module, 'format'):
|
||||
try:
|
||||
values['code'] = module.format(code)
|
||||
except stdnum.exceptions.ValidationError:
|
||||
pass
|
||||
if hasattr(module, 'compact'):
|
||||
try:
|
||||
values['code_compact'] = module.compact(code)
|
||||
except stdnum.exceptions.ValidationError:
|
||||
pass
|
||||
return values
|
||||
|
||||
def compute_fields(self, field_names=None):
|
||||
@@ -981,14 +974,12 @@ class Identifier(sequence_ordered(), DeactivableMixin, ModelSQL, ModelView):
|
||||
code_compact = getattr(self, 'code_compact', None)
|
||||
if (type := getattr(self, 'type', None)) and '_' in type and code:
|
||||
if module := get_cc_module(*type.split('_', 1)):
|
||||
if (hasattr(module, 'is_valid')
|
||||
and module.is_valid(code)):
|
||||
if hasattr(module, 'format'):
|
||||
code = module.format(code)
|
||||
if hasattr(module, 'compact'):
|
||||
code_compact = module.compact(code)
|
||||
else:
|
||||
code_compact = code
|
||||
if hasattr(module, 'format'):
|
||||
code = module.format(code)
|
||||
if hasattr(module, 'compact'):
|
||||
code_compact = module.compact(code)
|
||||
else:
|
||||
code_compact = code
|
||||
if getattr(self, 'code', None) != code:
|
||||
values['code'] = code
|
||||
if getattr(self, 'code_compact', None) != code_compact:
|
||||
|
||||
@@ -470,7 +470,7 @@ class Production(
|
||||
for input_ in self.inputs:
|
||||
if input_.state == 'cancelled':
|
||||
continue
|
||||
cost_price = input_.get_cost_price() or 0
|
||||
cost_price = input_.get_cost_price()
|
||||
cost += (Decimal(str(input_.internal_quantity)) * cost_price)
|
||||
return round_price(cost)
|
||||
|
||||
@@ -491,7 +491,7 @@ class Production(
|
||||
product = input_.product
|
||||
quantity = Uom.compute_qty(
|
||||
input_.unit, input_.quantity, product.default_uom)
|
||||
cost += Decimal(str(quantity)) * (product.cost_price or 0)
|
||||
cost += Decimal(str(quantity)) * product.cost_price
|
||||
return cost
|
||||
|
||||
@dualmethod
|
||||
@@ -516,20 +516,18 @@ class Production(
|
||||
for input_ in production.bom.inputs:
|
||||
quantity = input_.compute_quantity(factor)
|
||||
product = input_.product
|
||||
for line, quantity in input_.lines_for_quantity(quantity):
|
||||
move = production._move(
|
||||
'input', product, input_.unit, quantity)
|
||||
move.planned_date = input_date
|
||||
to_save.append(input_.prepare_move(production, move))
|
||||
move = production._move(
|
||||
'input', product, input_.unit, quantity)
|
||||
move.planned_date = input_date
|
||||
to_save.append(input_.prepare_move(production, move))
|
||||
|
||||
for output in production.bom.outputs:
|
||||
quantity = output.compute_quantity(factor)
|
||||
product = output.product
|
||||
for line, quantity in output.lines_for_quantity(quantity):
|
||||
move = production._move(
|
||||
'output', product, output.unit, quantity)
|
||||
move.planned_date = output_date
|
||||
to_save.append(output.prepare_move(production, move))
|
||||
move = production._move(
|
||||
'output', product, output.unit, quantity)
|
||||
move.planned_date = output_date
|
||||
to_save.append(output.prepare_move(production, move))
|
||||
Move.save(to_save)
|
||||
|
||||
@classmethod
|
||||
@@ -566,7 +564,7 @@ class Production(
|
||||
for input_ in production.inputs:
|
||||
if input_.state == 'cancelled':
|
||||
continue
|
||||
cost_price = input_.get_cost_price() or 0
|
||||
cost_price = input_.get_cost_price()
|
||||
input_quantities[input_.product] += (
|
||||
Decimal(str(input_.internal_quantity)))
|
||||
input_costs[input_.product] += (
|
||||
|
||||
@@ -322,10 +322,8 @@ class Move(Workflow, ModelSQL, ModelView):
|
||||
()),
|
||||
],
|
||||
states={
|
||||
'invisible': (
|
||||
~Eval('unit_price_required', False)
|
||||
& (Eval('unit_price', None) == None)), # noqa: E711
|
||||
'required': Eval('unit_price_required', False),
|
||||
'invisible': ~Eval('unit_price_required'),
|
||||
'required': Bool(Eval('unit_price_required')),
|
||||
'readonly': Eval('state') != 'draft',
|
||||
})
|
||||
unit_price_company = fields.Function(
|
||||
@@ -348,7 +346,7 @@ class Move(Workflow, ModelSQL, ModelView):
|
||||
()),
|
||||
],
|
||||
states={
|
||||
'invisible': ~Eval('cost_price_required', False),
|
||||
'invisible': ~Eval('cost_price_required'),
|
||||
'required': (
|
||||
(Eval('state') == 'done')
|
||||
& Eval('cost_price_required', False)),
|
||||
@@ -372,10 +370,8 @@ class Move(Workflow, ModelSQL, ModelView):
|
||||
()),
|
||||
],
|
||||
states={
|
||||
'invisible': (
|
||||
~Eval('unit_price_required')
|
||||
& (Eval('currency', None) == None)), # noqa: E711
|
||||
'required': Eval('unit_price_required', False),
|
||||
'invisible': ~Eval('unit_price_required'),
|
||||
'required': Bool(Eval('unit_price_required')),
|
||||
'readonly': Eval('state') != 'draft',
|
||||
},
|
||||
help="The currency in which the unit price is specified.")
|
||||
|
||||
@@ -154,13 +154,13 @@ class _InventoryMixin:
|
||||
output_clause = (
|
||||
(from_location.left >= left) & (from_location.right <= right))
|
||||
start_quantity = Sum(
|
||||
move.internal_quantity * Case((output_clause, -1), else_=1),
|
||||
move.quantity * Case((output_clause, -1), else_=1),
|
||||
filter_=(move_date < from_date))
|
||||
input_quantity = Sum(
|
||||
move.internal_quantity,
|
||||
move.quantity,
|
||||
filter_=input_clause & (move_date >= from_date))
|
||||
output_quantity = Sum(
|
||||
move.internal_quantity,
|
||||
move.quantity,
|
||||
filter_=output_clause & (move_date >= from_date))
|
||||
|
||||
date_column = Greatest(move_date, from_date)
|
||||
|
||||
@@ -50,8 +50,6 @@ Get currency::
|
||||
Create product::
|
||||
|
||||
>>> unit, = ProductUom.find([('name', '=', 'Unit')])
|
||||
>>> box2, = unit.duplicate(default={'factor': 2, 'rate': .5})
|
||||
>>> box10, = unit.duplicate(default={'factor': 10, 'rate': .1})
|
||||
|
||||
>>> template = ProductTemplate()
|
||||
>>> template.name = 'Product'
|
||||
@@ -98,8 +96,8 @@ Fill warehouse::
|
||||
>>> move.product = product
|
||||
>>> move.from_location = storage_loc
|
||||
>>> move.to_location = customer_loc
|
||||
>>> move.unit = box2
|
||||
>>> move.quantity = 1
|
||||
>>> move.unit = unit
|
||||
>>> move.quantity = 2
|
||||
>>> move.effective_date = dt.date(2025, 1, 10)
|
||||
>>> move.unit_price = Decimal('20.0000')
|
||||
>>> move.currency = currency
|
||||
@@ -113,8 +111,7 @@ Forecast some moves::
|
||||
>>> move.product = product
|
||||
>>> move.from_location = supplier_loc
|
||||
>>> move.to_location = storage_loc
|
||||
>>> move.unit = box10
|
||||
>>> move.quantity = 1
|
||||
>>> move.quantity = 10
|
||||
>>> move.effective_date = dt.date(2025, 1, 5)
|
||||
>>> move.unit_price = Decimal('10.0000')
|
||||
>>> move.currency = currency
|
||||
|
||||
@@ -281,18 +281,10 @@ class CreateDPDShipping(Wizard):
|
||||
cm3 = UoM(ModelData.get_id('product', 'uom_cubic_centimeter'))
|
||||
kg = UoM(ModelData.get_id('product', 'uom_kilogram'))
|
||||
|
||||
volume = sum(
|
||||
UoM.compute_qty(
|
||||
p.volume_uom, p.total_volume, shipment.volume_uom, round=False)
|
||||
for p in shipment.root_packages)
|
||||
volume = round(UoM.compute_qty(
|
||||
shipment.volume_uom, volume, cm3, round=False))
|
||||
weight = sum(
|
||||
UoM.compute_qty(
|
||||
p.weight_uom, p.total_weight, shipment.weight_uom, round=False)
|
||||
for p in shipment.root_packages)
|
||||
shipment.volume_uom, shipment.volume, cm3, round=False))
|
||||
weight = round(UoM.compute_qty(
|
||||
shipment.weight_uom, weight, kg, round=False), 2)
|
||||
shipment.weight_uom, shipment.weight, kg, round=False), 2)
|
||||
return {
|
||||
'generalShipmentData': {
|
||||
'identificationNumber': shipment.number,
|
||||
|
||||
@@ -136,9 +136,7 @@ Create Package Type::
|
||||
... name='Box',
|
||||
... length=10, length_uom=cm,
|
||||
... height=8, height_uom=cm,
|
||||
... width=5, width_uom=cm,
|
||||
... packaging_weight=50,
|
||||
... packaging_weight_uom=g)
|
||||
... width=5, width_uom=cm)
|
||||
>>> box.save()
|
||||
|
||||
Create a DPD Carrier and the related credential::
|
||||
|
||||
+4
-11
@@ -89,16 +89,13 @@
|
||||
function getScrollableAncestors(el) {
|
||||
let ancestors = [];
|
||||
let parent = el.parentElement;
|
||||
while (parent && (parent !== document.body ||
|
||||
!parent.classList.contains('modal'))) {
|
||||
while (parent && parent !== document.body) {
|
||||
if (isScrollable(parent)) {
|
||||
ancestors.push(parent);
|
||||
}
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
if (parent && !parent.classList.contains('modal')) {
|
||||
ancestors.push(window);
|
||||
}
|
||||
ancestors.push(window);
|
||||
return ancestors;
|
||||
}
|
||||
|
||||
@@ -2981,13 +2978,9 @@
|
||||
return ((a.isDate == b.isDate) &&
|
||||
(a.isDateTime == b.isDateTime) &&
|
||||
(a.valueOf() == b.valueOf()));
|
||||
} else if ((a instanceof Number) || (b instanceof Number)) {
|
||||
return (Number(a) === Number(b));
|
||||
} else {
|
||||
if (a instanceof Number) {
|
||||
a = a.valueOf();
|
||||
}
|
||||
if (b instanceof Number) {
|
||||
b = b.valueOf();
|
||||
}
|
||||
return (a === b);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1102,13 +1102,11 @@
|
||||
width = column.col.data('default-width');
|
||||
}
|
||||
if (width && width.endsWith('%')) {
|
||||
// Reverse the calculation from _set_column_width
|
||||
let factor = 1;
|
||||
width = parseInt(width.slice(0, -1), 10) / 100;
|
||||
if (column.attributes.expand) {
|
||||
factor += parseInt(column.attributes.expand, 10);
|
||||
width /= parseInt(column.attributes.expand, 10);
|
||||
}
|
||||
width = parseInt(width.slice(0, -1), 10);
|
||||
width = `${width / 100 / factor}em`;
|
||||
width = `${width}em`;
|
||||
}
|
||||
min_width.push(width);
|
||||
column.col.sao_show();
|
||||
|
||||
@@ -2969,44 +2969,6 @@
|
||||
var now = new Date();
|
||||
|
||||
[
|
||||
[[['x', '=', 42]], {'x': 42}, true],
|
||||
[[['x', '=', 42]], {'x': new Number(42)}, true],
|
||||
[[['x', '=', 42]], {'x': 0}, false],
|
||||
[[['x', '=', 42]], {'x': new Number(0)}, false],
|
||||
[[['x', '=', 42]], {'x': null}, false],
|
||||
[[['x', '=', new Number(42)]], {'x': 42}, true],
|
||||
[[['x', '=', new Number(42)]], {'x': new Number(42)}, true],
|
||||
[[['x', '=', new Number(42)]], {'x': 0}, false],
|
||||
[[['x', '=', new Number(42)]], {'x': new Number(0)}, false],
|
||||
[[['x', '=', new Number(42)]], {'x': null}, false],
|
||||
[[['x', '=', 0]], {'x': 0}, true],
|
||||
[[['x', '=', 0]], {'x': new Number(0)}, true],
|
||||
[[['x', '=', 0]], {'x': null}, false],
|
||||
[[['x', '=', new Number(0)]], {'x': 0}, true],
|
||||
[[['x', '=', new Number(0)]], {'x': new Number(0)}, true],
|
||||
[[['x', '=', new Number(0)]], {'x': null}, false],
|
||||
[[['x', '=', null]], {'x': 0}, false],
|
||||
[[['x', '=', null]], {'x': new Number(0)}, false],
|
||||
[[['x', '=', null]], {'x': null}, true],
|
||||
[[['x', '!=', 42]], {'x': 42}, false],
|
||||
[[['x', '!=', 42]], {'x': new Number(42)}, false],
|
||||
[[['x', '!=', 42]], {'x': 0}, true],
|
||||
[[['x', '!=', 42]], {'x': new Number(0)}, true],
|
||||
[[['x', '!=', 42]], {'x': null}, true],
|
||||
[[['x', '!=', new Number(42)]], {'x': 42}, false],
|
||||
[[['x', '!=', new Number(42)]], {'x': new Number(42)}, false],
|
||||
[[['x', '!=', new Number(42)]], {'x': 0}, true],
|
||||
[[['x', '!=', new Number(42)]], {'x': new Number(0)}, true],
|
||||
[[['x', '!=', new Number(42)]], {'x': null}, true],
|
||||
[[['x', '!=', 0]], {'x': 0}, false],
|
||||
[[['x', '!=', 0]], {'x': new Number(0)}, false],
|
||||
[[['x', '!=', 0]], {'x': null}, true],
|
||||
[[['x', '!=', new Number(0)]], {'x': 0}, false],
|
||||
[[['x', '!=', new Number(0)]], {'x': new Number(0)}, false],
|
||||
[[['x', '!=', new Number(0)]], {'x': null}, true],
|
||||
[[['x', '!=', null]], {'x': 0}, true],
|
||||
[[['x', '!=', null]], {'x': new Number(0)}, true],
|
||||
[[['x', '!=', null]], {'x': null}, false],
|
||||
[[['x', '>', 5]], {'x': 6}, true],
|
||||
[[['x', '>', 5]], {'x': 4}, false],
|
||||
[[['x', '>', null]], {'x': today}, false],
|
||||
|
||||
@@ -46,7 +46,4 @@ if db_names:
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
|
||||
if len(threads := threading.enumerate()) != 1:
|
||||
raise RuntimeError(
|
||||
f"There are {len(threads)} threads running after initializing"
|
||||
" the Pool")
|
||||
assert len(threads := threading.enumerate()) == 1, f"len({threads}) != 1"
|
||||
|
||||
@@ -256,8 +256,6 @@ class MemoryCache(BaseCache):
|
||||
|
||||
@classmethod
|
||||
def sync(cls, transaction):
|
||||
if not Pool().ready:
|
||||
return
|
||||
if cls._clean_last is None:
|
||||
cls._clean_last = dt.datetime.now()
|
||||
return
|
||||
@@ -300,7 +298,7 @@ class MemoryCache(BaseCache):
|
||||
cls._clean_last = dt.datetime.now()
|
||||
|
||||
def sync_since(self, value):
|
||||
return self._clean_last is not None and self._clean_last > value
|
||||
return self._clean_last > value
|
||||
|
||||
@classmethod
|
||||
def commit(cls, transaction):
|
||||
|
||||
@@ -104,7 +104,7 @@ class TrytonConfigParser(configparser.ConfigParser):
|
||||
self.add_section('bus')
|
||||
self.set('bus', 'allow_subscribe', 'False')
|
||||
self.set('bus', 'long_polling_timeout', str(5 * 60))
|
||||
self.set('bus', 'cache_timeout', str(5 * 60))
|
||||
self.set('bus', 'cache_timeout', '5')
|
||||
self.set('bus', 'select_timeout', '5')
|
||||
self.add_section('report')
|
||||
self.add_section('html')
|
||||
|
||||
@@ -345,11 +345,7 @@ class ActionMixin(ModelSQL):
|
||||
|
||||
action = fields.Many2One(
|
||||
'ir.action', "Action",
|
||||
required=True, ondelete='CASCADE',
|
||||
states={
|
||||
'required': Eval('id', -1) >= 0,
|
||||
'readonly': Eval('id', -1) >= 0,
|
||||
})
|
||||
required=True, readonly=True, ondelete='CASCADE')
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
@@ -460,9 +456,8 @@ class ActionMixin(ModelSQL):
|
||||
values = values.copy()
|
||||
for field in values:
|
||||
if field in Action._fields:
|
||||
if field != 'action':
|
||||
action_values[field] = values[field]
|
||||
elif hasattr(getattr(cls, field), 'set'):
|
||||
action_values[field] = values[field]
|
||||
if hasattr(getattr(cls, field), 'set'):
|
||||
later[field] = values[field]
|
||||
for field in later:
|
||||
del values[field]
|
||||
@@ -473,7 +468,7 @@ class ActionMixin(ModelSQL):
|
||||
if database.nextid(transaction.connection, cls._table):
|
||||
database.setnextid(transaction.connection, cls._table,
|
||||
database.currid(transaction.connection, Action._table))
|
||||
if not values.get('action'):
|
||||
if 'action' not in values:
|
||||
action, = Action.create([action_values])
|
||||
values['action'] = action.id
|
||||
else:
|
||||
|
||||
@@ -464,7 +464,7 @@ class Report(URLMixin, PoolBase):
|
||||
time.sleep(0.02 * (retry - count))
|
||||
try:
|
||||
subprocess.run(args, timeout=timeout, check=True)
|
||||
except (subprocess.SubprocessError, OSError):
|
||||
except subprocess.CalledProcessError:
|
||||
if count:
|
||||
continue
|
||||
logger.error(
|
||||
|
||||
@@ -884,63 +884,6 @@ class DomainInversionTestCase(TestCase):
|
||||
unique_value(domain, single_value=False), (False, None, None))
|
||||
|
||||
def test_evaldomain(self):
|
||||
|
||||
domain = [['x', '=', 42]]
|
||||
self.assertTrue(eval_domain(domain, {'x': 42}))
|
||||
self.assertTrue(eval_domain(domain, {'x': Decimal('42')}))
|
||||
self.assertFalse(eval_domain(domain, {'x': 0}))
|
||||
self.assertFalse(eval_domain(domain, {'x': Decimal('0')}))
|
||||
self.assertFalse(eval_domain(domain, {'x': None}))
|
||||
|
||||
domain = [['x', '=', Decimal('42')]]
|
||||
self.assertTrue(eval_domain(domain, {'x': 42}))
|
||||
self.assertTrue(eval_domain(domain, {'x': Decimal('42')}))
|
||||
self.assertFalse(eval_domain(domain, {'x': 0}))
|
||||
self.assertFalse(eval_domain(domain, {'x': Decimal('0')}))
|
||||
self.assertFalse(eval_domain(domain, {'x': None}))
|
||||
|
||||
domain = [['x', '=', 0]]
|
||||
self.assertTrue(eval_domain(domain, {'x': 0}))
|
||||
self.assertTrue(eval_domain(domain, {'x': Decimal('0')}))
|
||||
self.assertFalse(eval_domain(domain, {'x': None}))
|
||||
|
||||
domain = [['x', '=', Decimal('0')]]
|
||||
self.assertTrue(eval_domain(domain, {'x': 0}))
|
||||
self.assertTrue(eval_domain(domain, {'x': Decimal('0')}))
|
||||
self.assertFalse(eval_domain(domain, {'x': None}))
|
||||
|
||||
domain = [['x', '=', None]]
|
||||
self.assertFalse(eval_domain(domain, {'x': 0}))
|
||||
self.assertFalse(eval_domain(domain, {'x': Decimal('0')}))
|
||||
self.assertTrue(eval_domain(domain, {'x': None}))
|
||||
|
||||
domain = [['x', '!=', 42]]
|
||||
self.assertFalse(eval_domain(domain, {'x': 42}))
|
||||
self.assertFalse(eval_domain(domain, {'x': Decimal('42')}))
|
||||
self.assertTrue(eval_domain(domain, {'x': Decimal('0')}))
|
||||
self.assertTrue(eval_domain(domain, {'x': None}))
|
||||
|
||||
domain = [['x', '!=', Decimal('42')]]
|
||||
self.assertFalse(eval_domain(domain, {'x': 42}))
|
||||
self.assertFalse(eval_domain(domain, {'x': Decimal('42')}))
|
||||
self.assertTrue(eval_domain(domain, {'x': Decimal('0')}))
|
||||
self.assertTrue(eval_domain(domain, {'x': None}))
|
||||
|
||||
domain = [['x', '!=', 0]]
|
||||
self.assertFalse(eval_domain(domain, {'x': 0}))
|
||||
self.assertFalse(eval_domain(domain, {'x': Decimal('0')}))
|
||||
self.assertTrue(eval_domain(domain, {'x': None}))
|
||||
|
||||
domain = [['x', '!=', Decimal('0')]]
|
||||
self.assertFalse(eval_domain(domain, {'x': 0}))
|
||||
self.assertFalse(eval_domain(domain, {'x': Decimal('0')}))
|
||||
self.assertTrue(eval_domain(domain, {'x': None}))
|
||||
|
||||
domain = [['x', '!=', None]]
|
||||
self.assertTrue(eval_domain(domain, {'x': 0}))
|
||||
self.assertTrue(eval_domain(domain, {'x': Decimal('0')}))
|
||||
self.assertFalse(eval_domain(domain, {'x': None}))
|
||||
|
||||
domain = [['x', '>', 5]]
|
||||
self.assertTrue(eval_domain(domain, {'x': 6}))
|
||||
self.assertFalse(eval_domain(domain, {'x': 4}))
|
||||
|
||||
Reference in New Issue
Block a user