Initial commit: L10N_Asia Pacific packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:52 +02:00
commit 54c86b612c
828 changed files with 58224 additions and 0 deletions

View file

@ -0,0 +1,66 @@
# Indonesia E-faktur
E-Faktur Menu(Indonesia)
Format : 010.000-16.00000001
* 2 (dua) digit pertama adalah Kode Transaksi
* 1 (satu) digit berikutnya adalah Kode Status
* 3 (tiga) digit berikutnya adalah Kode Cabang
* 2 (dua) digit pertama adalah Tahun Penerbitan
* 8 (delapan) digit berikutnya adalah Nomor Urut
To be able to export customer invoices as e-Faktur,
you need to put the ranges of numbers you were assigned
by the government in Accounting > Customers > e-Faktur
When you validate an invoice, where the partner has the ID PKP
field checked, a tax number will be assigned to that invoice.
Afterwards, you can filter the invoices still to export in the
invoices list and click on Action > Download e-Faktur to download
the csv and upload it to the site of the government.
You can replace an already sent invoice by another by indicating
the replaced invoice and the new one and you can reset an invoice
you have not already sent to the government to reuse its number.
## Installation
```bash
pip install odoo-bringout-oca-ocb-l10n_id_efaktur
```
## Dependencies
This addon depends on:
- l10n_id
## Manifest Information
- **Name**: Indonesia E-faktur
- **Version**: 1.0
- **Category**: Accounting/Localizations/EDI
- **License**: LGPL-3
- **Installable**: True
## Source
Based on [OCA/OCB](https://github.com/OCA/OCB) branch 16.0, addon `l10n_id_efaktur`.
## License
This package maintains the original LGPL-3 license from the upstream Odoo project.
## Documentation
- Overview: doc/OVERVIEW.md
- Architecture: doc/ARCHITECTURE.md
- Models: doc/MODELS.md
- Controllers: doc/CONTROLLERS.md
- Wizards: doc/WIZARDS.md
- Install: doc/INSTALL.md
- Usage: doc/USAGE.md
- Configuration: doc/CONFIGURATION.md
- Dependencies: doc/DEPENDENCIES.md
- Troubleshooting: doc/TROUBLESHOOTING.md
- FAQ: doc/FAQ.md

View file

@ -0,0 +1,32 @@
# Architecture
```mermaid
flowchart TD
U[Users] -->|HTTP| V[Views and QWeb Templates]
V --> C[Controllers]
V --> W[Wizards Transient Models]
C --> M[Models and ORM]
W --> M
M --> R[Reports]
DX[Data XML] --> M
S[Security ACLs and Groups] -. enforces .-> M
subgraph L10n_id_efaktur Module - l10n_id_efaktur
direction LR
M:::layer
W:::layer
C:::layer
V:::layer
R:::layer
S:::layer
DX:::layer
end
classDef layer fill:#eef8ff,stroke:#6ea8fe,stroke-width:1px
```
Notes
- Views include tree/form/kanban templates and report templates.
- Controllers provide website/portal routes when present.
- Wizards are UI flows implemented with `models.TransientModel`.
- Data XML loads data/demo records; Security defines groups and access.

View file

@ -0,0 +1,3 @@
# Configuration
Refer to Odoo settings for l10n_id_efaktur. Configure related models, access rights, and options as needed.

View file

@ -0,0 +1,3 @@
# Controllers
This module does not define custom HTTP controllers.

View file

@ -0,0 +1,5 @@
# Dependencies
This addon depends on:
- [l10n_id](../../odoo-bringout-oca-ocb-l10n_id)

View file

@ -0,0 +1,4 @@
# FAQ
- Q: Which Odoo version? A: 16.0 (OCA/OCB packaged).
- Q: How to enable? A: Start server with --addon l10n_id_efaktur or install in UI.

View file

@ -0,0 +1,7 @@
# Install
```bash
pip install odoo-bringout-oca-ocb-l10n_id_efaktur"
# or
uv pip install odoo-bringout-oca-ocb-l10n_id_efaktur"
```

View file

@ -0,0 +1,15 @@
# Models
Detected core models and extensions in l10n_id_efaktur.
```mermaid
classDiagram
class l10n_id_efaktur_efaktur_range
class account_move
class res_config_settings
class res_partner
```
Notes
- Classes show model technical names; fields omitted for brevity.
- Items listed under _inherit are extensions of existing models.

View file

@ -0,0 +1,6 @@
# Overview
Packaged Odoo addon: l10n_id_efaktur. Provides features documented in upstream Odoo 16 under this addon.
- Source: OCA/OCB 16.0, addon l10n_id_efaktur
- License: LGPL-3

View file

@ -0,0 +1,3 @@
# Reports
This module does not define custom reports.

View file

@ -0,0 +1,34 @@
# Security
Access control and security definitions in l10n_id_efaktur.
## Access Control Lists (ACLs)
Model access permissions defined in:
- **[ir.model.access.csv](../l10n_id_efaktur/security/ir.model.access.csv)**
- 1 model access rules
## Record Rules
Row-level security rules defined in:
```mermaid
graph TB
subgraph "Security Layers"
A[Users] --> B[Groups]
B --> C[Access Control Lists]
C --> D[Models]
B --> E[Record Rules]
E --> F[Individual Records]
end
```
Security files overview:
- **[ir.model.access.csv](../l10n_id_efaktur/security/ir.model.access.csv)**
- Model access permissions (CRUD rights)
Notes
- Access Control Lists define which groups can access which models
- Record Rules provide row-level security (filter records by user/group)
- Security groups organize users and define permission sets
- All security is enforced at the ORM level by Odoo

View file

@ -0,0 +1,5 @@
# Troubleshooting
- Ensure Python and Odoo environment matches repo guidance.
- Check database connectivity and logs if startup fails.
- Validate that dependent addons listed in DEPENDENCIES.md are installed.

View file

@ -0,0 +1,7 @@
# Usage
Start Odoo including this addon (from repo root):
```bash
python3 scripts/nix_odoo_web_server.py --db-name mydb --addon l10n_id_efaktur
```

View file

@ -0,0 +1,3 @@
# Wizards
This module does not include UI wizards.

View file

@ -0,0 +1,4 @@
# -*- encoding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models

View file

@ -0,0 +1,43 @@
# -*- encoding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Indonesia E-faktur',
'icon': '/l10n_id/static/description/icon.png',
'version': '1.0',
'description': """
E-Faktur Menu(Indonesia)
Format : 010.000-16.00000001
* 2 (dua) digit pertama adalah Kode Transaksi
* 1 (satu) digit berikutnya adalah Kode Status
* 3 (tiga) digit berikutnya adalah Kode Cabang
* 2 (dua) digit pertama adalah Tahun Penerbitan
* 8 (delapan) digit berikutnya adalah Nomor Urut
To be able to export customer invoices as e-Faktur,
you need to put the ranges of numbers you were assigned
by the government in Accounting > Customers > e-Faktur
When you validate an invoice, where the partner has the ID PKP
field checked, a tax number will be assigned to that invoice.
Afterwards, you can filter the invoices still to export in the
invoices list and click on Action > Download e-Faktur to download
the csv and upload it to the site of the government.
You can replace an already sent invoice by another by indicating
the replaced invoice and the new one and you can reset an invoice
you have not already sent to the government to reuse its number.
""",
'category': 'Accounting/Localizations/EDI',
'depends': ['l10n_id'],
'data': [
'security/ir.model.access.csv',
'views/account_move_views.xml',
'views/efaktur_views.xml',
'views/res_config_settings_views.xml',
'views/res_partner_views.xml',
],
'installable': True,
'auto_install': True,
'license': 'LGPL-3',
}

View file

@ -0,0 +1,447 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * l10n_id_efaktur
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-06 01:39+0000\n"
"PO-Revision-Date: 2025-02-06 01:39+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: l10n_id_efaktur
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__account_move__l10n_id_kode_transaksi__01
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__res_partner__l10n_id_kode_transaksi__01
msgid "01 Kepada Pihak yang Bukan Pemungut PPN (Customer Biasa)"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__account_move__l10n_id_kode_transaksi__02
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__res_partner__l10n_id_kode_transaksi__02
msgid "02 Kepada Pemungut Bendaharawan (Dinas Kepemerintahan)"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__account_move__l10n_id_kode_transaksi__03
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__res_partner__l10n_id_kode_transaksi__03
msgid "03 Kepada Pemungut Selain Bendaharawan (BUMN)"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__account_move__l10n_id_kode_transaksi__04
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__res_partner__l10n_id_kode_transaksi__04
msgid "04 DPP Nilai Lain (PPN 1%)"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__account_move__l10n_id_kode_transaksi__05
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__res_partner__l10n_id_kode_transaksi__05
msgid "05 Besaran Tertentu"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__account_move__l10n_id_kode_transaksi__06
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__res_partner__l10n_id_kode_transaksi__06
msgid "06 to individuals holding foreign passports"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__account_move__l10n_id_kode_transaksi__07
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__res_partner__l10n_id_kode_transaksi__07
msgid ""
"07 Penyerahan yang PPN-nya Tidak Dipungut (Kawasan Ekonomi Khusus/ Batam)"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__account_move__l10n_id_kode_transaksi__08
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__res_partner__l10n_id_kode_transaksi__08
msgid "08 Penyerahan yang PPN-nya Dibebaskan (Impor Barang Tertentu)"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__account_move__l10n_id_kode_transaksi__09
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__res_partner__l10n_id_kode_transaksi__09
msgid "09 Penyerahan Aktiva ( Pasal 16D UU PPN )"
msgstr ""
#. module: l10n_id_efaktur
#: model_terms:ir.ui.view,arch_db:l10n_id_efaktur.res_config_settings_view_form
msgid ""
"<span class=\"fa fa-lg fa-building-o\" title=\"Values set here are company-"
"specific.\" aria-label=\"Values set here are company-specific.\" "
"groups=\"base.group_multi_company\" role=\"img\"/>"
msgstr ""
#. module: l10n_id_efaktur
#. odoo-python
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid "A tax number must begin by a valid Kode Transaksi"
msgstr ""
#. module: l10n_id_efaktur
#. odoo-python
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid "A tax number should have 16 digits"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_l10n_id_efaktur_efaktur_range__available
msgid "Available"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model,name:l10n_id_efaktur.model_l10n_id_efaktur_efaktur_range
msgid "Available E-faktur range"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_bank_statement_line__l10n_id_csv_created
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_move__l10n_id_csv_created
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_payment__l10n_id_csv_created
msgid "CSV Created"
msgstr ""
#. module: l10n_id_efaktur
#. odoo-python
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid ""
"Cannot mix VAT subject and Non-VAT subject items in the same invoice with "
"this kode transaksi."
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_l10n_id_efaktur_efaktur_range__company_id
msgid "Company"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model,name:l10n_id_efaktur.model_res_config_settings
msgid "Config Settings"
msgstr ""
#. module: l10n_id_efaktur
#. odoo-python
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid "Connect %(move_number)s with E-faktur to download this report"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model,name:l10n_id_efaktur.model_res_partner
msgid "Contact"
msgstr ""
#. module: l10n_id_efaktur
#. odoo-python
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid "Could not download E-faktur in draft state"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_l10n_id_efaktur_efaktur_range__create_uid
msgid "Created by"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_l10n_id_efaktur_efaktur_range__create_date
msgid "Created on"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_l10n_id_efaktur_efaktur_range__display_name
msgid "Display Name"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.actions.server,name:l10n_id_efaktur.dowload_efaktur_action
msgid "Download e-Faktur"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields,help:l10n_id_efaktur.field_account_bank_statement_line__l10n_id_kode_transaksi
#: model:ir.model.fields,help:l10n_id_efaktur.field_account_move__l10n_id_kode_transaksi
#: model:ir.model.fields,help:l10n_id_efaktur.field_account_payment__l10n_id_kode_transaksi
#: model:ir.model.fields,help:l10n_id_efaktur.field_res_partner__l10n_id_kode_transaksi
#: model:ir.model.fields,help:l10n_id_efaktur.field_res_users__l10n_id_kode_transaksi
msgid "Dua digit pertama nomor pajak"
msgstr ""
#. module: l10n_id_efaktur
#. odoo-python
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid "E-faktur is not available for invoices without any taxes."
msgstr ""
#. module: l10n_id_efaktur
#. odoo-python
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid "Edit Customer Information"
msgstr ""
#. module: l10n_id_efaktur
#: model_terms:ir.ui.view,arch_db:l10n_id_efaktur.efaktur_tree_view
msgid "Efaktur Number"
msgstr ""
#. module: l10n_id_efaktur
#. odoo-python
#: code:addons/l10n_id_efaktur/models/efaktur.py:0
#, python-format
msgid "Efaktur interleaving range detected"
msgstr ""
#. module: l10n_id_efaktur
#: model_terms:ir.ui.view,arch_db:l10n_id_efaktur.account_move_efaktur_form_view
msgid "Electronic Tax"
msgstr ""
#. module: l10n_id_efaktur
#. odoo-python
#: code:addons/l10n_id_efaktur/models/efaktur.py:0
#, python-format
msgid "First 5 digits should be same in Start Number and End Number."
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_l10n_id_efaktur_efaktur_range__id
msgid "ID"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_res_partner__l10n_id_pkp
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_res_users__l10n_id_pkp
msgid "ID PKP"
msgstr ""
#. module: l10n_id_efaktur
#: model_terms:ir.actions.act_window,help:l10n_id_efaktur.efaktur_invoice_action
msgid ""
"In order to be able to export customer invoices as e-Faktur\n"
" for the Indonesian government, you need to put here the ranges\n"
" of numbers you were assigned by the government.\n"
" When you validate an invoice, a number will be assigned based on these ranges.\n"
" Afterwards, you can filter the invoices still to export in the\n"
" invoices list and click on Action &gt; Download e-Faktur"
msgstr ""
#. module: l10n_id_efaktur
#: model_terms:ir.ui.view,arch_db:l10n_id_efaktur.res_config_settings_view_form
msgid "Indonesian Localization"
msgstr ""
#. module: l10n_id_efaktur
#: model_terms:ir.ui.view,arch_db:l10n_id_efaktur.res_partner_tax_form_view
msgid "Indonesian Taxes"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model,name:l10n_id_efaktur.model_account_move
msgid "Journal Entry"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_bank_statement_line__l10n_id_kode_transaksi
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_move__l10n_id_kode_transaksi
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_payment__l10n_id_kode_transaksi
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_res_partner__l10n_id_kode_transaksi
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_res_users__l10n_id_kode_transaksi
msgid "Kode Transaksi"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_bank_statement_line__l10n_id_attachment_id
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_move__l10n_id_attachment_id
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_payment__l10n_id_attachment_id
msgid "L10N Id Attachment"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_bank_statement_line__l10n_id_need_kode_transaksi
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_move__l10n_id_need_kode_transaksi
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_payment__l10n_id_need_kode_transaksi
msgid "L10N Id Need Kode Transaksi"
msgstr ""
#. module: l10n_id_efaktur
#. odoo-python
#: code:addons/l10n_id_efaktur/models/efaktur.py:0
#, python-format
msgid ""
"Last 8 digits of End Number should be greater than the last 8 digit of Start"
" Number"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_l10n_id_efaktur_efaktur_range____last_update
msgid "Last Modified on"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_l10n_id_efaktur_efaktur_range__write_uid
msgid "Last Updated by"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_l10n_id_efaktur_efaktur_range__write_date
msgid "Last Updated on"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_l10n_id_efaktur_efaktur_range__max
msgid "Max"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_l10n_id_efaktur_efaktur_range__min
msgid "Min"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_res_partner__l10n_id_nik
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_res_users__l10n_id_nik
msgid "NIK"
msgstr ""
#. module: l10n_id_efaktur
#. odoo-python
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid ""
"Please make sure that you've input the appropriate NPWP or NIK for the "
"following customer"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_bank_statement_line__l10n_id_replace_invoice_id
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_move__l10n_id_replace_invoice_id
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_payment__l10n_id_replace_invoice_id
msgid "Replace Invoice"
msgstr ""
#. module: l10n_id_efaktur
#. odoo-python
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid ""
"Replacement invoice only for invoices on which the e-Faktur is generated. "
msgstr ""
#. module: l10n_id_efaktur
#: model_terms:ir.ui.view,arch_db:l10n_id_efaktur.account_move_efaktur_form_view
msgid "Reset E-Faktur"
msgstr ""
#. module: l10n_id_efaktur
#. odoo-python
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid "Some documents are not Customer Invoices"
msgstr ""
#. module: l10n_id_efaktur
#. odoo-python
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid "Some documents don't have a transaction code"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_res_config_settings__l10n_id_tax_address
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_res_partner__l10n_id_tax_address
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_res_users__l10n_id_tax_address
msgid "Tax Address"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_res_config_settings__l10n_id_tax_name
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_res_partner__l10n_id_tax_name
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_res_users__l10n_id_tax_name
msgid "Tax Name"
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_bank_statement_line__l10n_id_tax_number
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_move__l10n_id_tax_number
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_payment__l10n_id_tax_number
msgid "Tax Number"
msgstr ""
#. module: l10n_id_efaktur
#. odoo-python
#: code:addons/l10n_id_efaktur/models/efaktur.py:0
#, python-format
msgid "The difference between the two numbers must not be greater than 10.000"
msgstr ""
#. module: l10n_id_efaktur
#. odoo-python
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid "The third digit of a tax number must be 0 or 1"
msgstr ""
#. module: l10n_id_efaktur
#. odoo-python
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid ""
"There is no Efaktur number available. Please configure the range you get "
"from the government in the e-Faktur menu. "
msgstr ""
#. module: l10n_id_efaktur
#. odoo-python
#: code:addons/l10n_id_efaktur/models/efaktur.py:0
#, python-format
msgid "There should be 13 digits in each number."
msgstr ""
#. module: l10n_id_efaktur
#: model_terms:ir.ui.view,arch_db:l10n_id_efaktur.view_account_invoice_filter
msgid "To Generate e-Faktur"
msgstr ""
#. module: l10n_id_efaktur
#: model_terms:ir.ui.view,arch_db:l10n_id_efaktur.efaktur_tree_view
msgid "Total Available"
msgstr ""
#. module: l10n_id_efaktur
#. odoo-python
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid ""
"You can only change the number manually for a Vendor Bills and Credit Notes"
msgstr ""
#. module: l10n_id_efaktur
#. odoo-python
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid "You have already generated the tax report for this document: %s"
msgstr ""
#. module: l10n_id_efaktur
#. odoo-python
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid "You need to put a Kode Transaksi for this partner."
msgstr ""
#. module: l10n_id_efaktur
#: model:ir.actions.act_window,name:l10n_id_efaktur.efaktur_invoice_action
#: model:ir.ui.menu,name:l10n_id_efaktur.menu_efaktur_action
msgid "e-Faktur"
msgstr ""

View file

@ -0,0 +1,440 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * l10n_id_efaktur
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-12-04 02:05+0000\n"
"PO-Revision-Date: 2023-12-04 19:08+0800\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: id\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 3.4.1\n"
#. module: l10n_id_efaktur
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__account_move__l10n_id_kode_transaksi__01
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__res_partner__l10n_id_kode_transaksi__01
msgid "01 To the Parties that is not VAT Collector (Regular Customers)"
msgstr "01 Kepada Pihak yang Bukan Pemungut PPN (Customer Biasa)"
#. module: l10n_id_efaktur
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__account_move__l10n_id_kode_transaksi__02
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__res_partner__l10n_id_kode_transaksi__02
msgid "02 To the Treasurer"
msgstr "02 Kepada Pemungut Bendaharawan (Dinas Kepemerintahan)"
#. module: l10n_id_efaktur
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__account_move__l10n_id_kode_transaksi__03
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__res_partner__l10n_id_kode_transaksi__03
msgid "03 To other VAT Collectors other than the Treasurer"
msgstr "03 Kepada Pemungut Selain Bendaharawan (BUMN)"
#. module: l10n_id_efaktur
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__account_move__l10n_id_kode_transaksi__04
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__res_partner__l10n_id_kode_transaksi__04
msgid "04 Other Value of VAT Imposition Base"
msgstr "04 DPP Nilai Lain (PPN 1%)"
#. module: l10n_id_efaktur
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__account_move__l10n_id_kode_transaksi__05
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__res_partner__l10n_id_kode_transaksi__05
msgid "05 Specified Amount (Article 9A Paragraph (1) VAT Law)')"
msgstr "05 Besaran Tertentu"
#. module: l10n_id_efaktur
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__account_move__l10n_id_kode_transaksi__06
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__res_partner__l10n_id_kode_transaksi__06
msgid "06 to individuals holding foreign passports"
msgstr "06 Penyerahan Lainnya (Turis Asing)"
#. module: l10n_id_efaktur
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__account_move__l10n_id_kode_transaksi__07
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__res_partner__l10n_id_kode_transaksi__07
msgid ""
"07 Deliveries that the VAT is not Collected"
msgstr ""
"07 Penyerahan yang PPN-nya Tidak Dipungut (Kawasan Ekonomi Khusus/ Batam)"
#. module: l10n_id_efaktur
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__account_move__l10n_id_kode_transaksi__08
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__res_partner__l10n_id_kode_transaksi__08
msgid "08 Deliveries that the VAT is Exempted"
msgstr "08 Penyerahan yang PPN-nya Dibebaskan (Impor Barang Tertentu)"
#. module: l10n_id_efaktur
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__account_move__l10n_id_kode_transaksi__09
#: model:ir.model.fields.selection,name:l10n_id_efaktur.selection__res_partner__l10n_id_kode_transaksi__09
msgid "09 Deliveries of Assets (Article 16D of VAT Law)"
msgstr "09 Penyerahan Aktiva ( Pasal 16D UU PPN )"
#. module: l10n_id_efaktur
#: model_terms:ir.ui.view,arch_db:l10n_id_efaktur.res_config_settings_view_form
msgid ""
"<span class=\"fa fa-lg fa-building-o\" title=\"Values set here are company-"
"specific.\" aria-label=\"Values set here are company-specific.\" "
"groups=\"base.group_multi_company\" role=\"img\"/>"
msgstr ""
"<span class=\"fa fa-lg fa-building-o\" title=\"Nilai yang ditentukan di "
"sini spesifik pada perusahaan.\" aria-label=\"Nilai yang ditentukan di sini "
"spesifik pada perusahaan.\" groups=\"base.group_multi_company\" "
"role=\"img\"/>"
#. module: l10n_id_efaktur
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid "A tax number must begin by a valid Kode Transaksi"
msgstr "Nomor pajak harus dimulai dengan Kode Transaksi yang valid"
#. module: l10n_id_efaktur
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid "A tax number should have 16 digits"
msgstr "Nomor pajak harus memiliki 16 angka"
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_l10n_id_efaktur_efaktur_range__available
msgid "Available"
msgstr "Tersedia"
#. module: l10n_id_efaktur
#: model:ir.model,name:l10n_id_efaktur.model_l10n_id_efaktur_efaktur_range
msgid "Available E-faktur range"
msgstr "Rentang E-faktur yang tersedia"
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_bank_statement_line__l10n_id_csv_created
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_move__l10n_id_csv_created
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_payment__l10n_id_csv_created
msgid "CSV Created"
msgstr "CSV Dibuat"
#. module: l10n_id_efaktur
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid ""
"Cannot mix VAT subject and Non-VAT subject items in the same invoice with "
"this kode transaksi."
msgstr ""
"Tidak dapat menggabungkan subjek PPN dan subjek Bukan PPN dalam faktur yang "
"sama dengan kode transaksi ini."
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_l10n_id_efaktur_efaktur_range__company_id
msgid "Company"
msgstr "Perusahaan"
#. module: l10n_id_efaktur
#: model:ir.model,name:l10n_id_efaktur.model_res_config_settings
msgid "Config Settings"
msgstr "Pengaturan Konfigurasi"
#. module: l10n_id_efaktur
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid "Connect %(move_number)s with E-faktur to download this report"
msgstr "Hubungkan %(move_number)s dengan E-faktur untuk mengunduh laporan ini"
#. module: l10n_id_efaktur
#: model:ir.model,name:l10n_id_efaktur.model_res_partner
msgid "Contact"
msgstr "Kontak"
#. module: l10n_id_efaktur
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid "Could not download E-faktur in draft state"
msgstr "Tidak dapat mengunduh E-faktur dalam status draft"
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_l10n_id_efaktur_efaktur_range__create_uid
msgid "Created by"
msgstr "Dibuat oleh"
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_l10n_id_efaktur_efaktur_range__create_date
msgid "Created on"
msgstr "Dibuat pada"
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_l10n_id_efaktur_efaktur_range__display_name
msgid "Display Name"
msgstr "Nama Tampilan"
#. module: l10n_id_efaktur
#: model:ir.actions.server,name:l10n_id_efaktur.dowload_efaktur_action
msgid "Download e-Faktur"
msgstr "Unduh e-Faktur"
#. module: l10n_id_efaktur
#: model:ir.model.fields,help:l10n_id_efaktur.field_account_bank_statement_line__l10n_id_kode_transaksi
#: model:ir.model.fields,help:l10n_id_efaktur.field_account_move__l10n_id_kode_transaksi
#: model:ir.model.fields,help:l10n_id_efaktur.field_account_payment__l10n_id_kode_transaksi
#: model:ir.model.fields,help:l10n_id_efaktur.field_res_partner__l10n_id_kode_transaksi
#: model:ir.model.fields,help:l10n_id_efaktur.field_res_users__l10n_id_kode_transaksi
msgid "Dua digit pertama nomor pajak"
msgstr "Dua digit pertama nomor pajak"
#. module: l10n_id_efaktur
#. odoo-python
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid "E-faktur is not available for invoices without any taxes."
msgstr "E-faktur tidak tersedia untuk faktur tanpa pajak apapun."
#. module: l10n_id_efaktur
#: model_terms:ir.ui.view,arch_db:l10n_id_efaktur.efaktur_tree_view
msgid "Efaktur Number"
msgstr "Nomor Efaktur"
#. module: l10n_id_efaktur
#: code:addons/l10n_id_efaktur/models/efaktur.py:0
#, python-format
msgid "Efaktur interleaving range detected"
msgstr "Interleaving range Efaktur dideteksi"
#. module: l10n_id_efaktur
#: model_terms:ir.ui.view,arch_db:l10n_id_efaktur.account_move_efaktur_form_view
msgid "Electronic Tax"
msgstr "Pajak Elektronik"
#. module: l10n_id_efaktur
#: code:addons/l10n_id_efaktur/models/efaktur.py:0
#, python-format
msgid "First 5 digits should be same in Start Number and End Number."
msgstr "5 angka pertama harus sama dengan Nomor Awal dan Nomor Akhir."
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_l10n_id_efaktur_efaktur_range__id
msgid "ID"
msgstr "ID"
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_res_partner__l10n_id_pkp
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_res_users__l10n_id_pkp
msgid "ID PKP"
msgstr "ID PKP"
#. module: l10n_id_efaktur
#: model_terms:ir.actions.act_window,help:l10n_id_efaktur.efaktur_invoice_action
msgid ""
"In order to be able to export customer invoices as e-Faktur\n"
" for the Indonesian government, you need to put here the "
"ranges\n"
" of numbers you were assigned by the government.\n"
" When you validate an invoice, a number will be assigned "
"based on these ranges.\n"
" Afterwards, you can filter the invoices still to export "
"in the\n"
" invoices list and click on Action &gt; Download e-Faktur"
msgstr ""
"Agar dapat mengekspor faktur pelanggan sebagai e-Faktur\n"
" untuk pemerintah Indonesia, Anda harus menaruh di sini "
"rentang\n"
" nomor yang diberikan ke Anda oleh pemerintah.\n"
" Saat Anda memvalidasi faktur, nomor akan diberikan ke "
"Anda berdasarkan rentang-rentang ini.\n"
" Setelahnya, Anda dapat memfilter faktur yang masih akan "
"diekspor di\n"
" daftar faktur dan klik pada Action &gt; Unduh e-Faktur"
#. module: l10n_id_efaktur
#: model_terms:ir.ui.view,arch_db:l10n_id_efaktur.res_config_settings_view_form
msgid "Indonesian Localization"
msgstr "Lokalisasi Indonesia"
#. module: l10n_id_efaktur
#: model_terms:ir.ui.view,arch_db:l10n_id_efaktur.res_partner_tax_form_view
msgid "Indonesian Taxes"
msgstr "Pajak-Pajak Indonesia"
#. module: l10n_id_efaktur
#: model:ir.model,name:l10n_id_efaktur.model_account_move
msgid "Journal Entry"
msgstr "Entri Jurnal"
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_bank_statement_line__l10n_id_kode_transaksi
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_move__l10n_id_kode_transaksi
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_payment__l10n_id_kode_transaksi
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_res_partner__l10n_id_kode_transaksi
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_res_users__l10n_id_kode_transaksi
msgid "Kode Transaksi"
msgstr "Kode Transaksi"
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_bank_statement_line__l10n_id_attachment_id
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_move__l10n_id_attachment_id
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_payment__l10n_id_attachment_id
msgid "L10N Id Attachment"
msgstr "Lampiran L10N Id"
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_bank_statement_line__l10n_id_need_kode_transaksi
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_move__l10n_id_need_kode_transaksi
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_payment__l10n_id_need_kode_transaksi
msgid "L10N Id Need Kode Transaksi"
msgstr "L10N Id Membutuhkan Kode Transaksi"
#. module: l10n_id_efaktur
#: code:addons/l10n_id_efaktur/models/efaktur.py:0
#, python-format
msgid ""
"Last 8 digits of End Number should be greater than the last 8 digit of "
"Start Number"
msgstr ""
"8 angka terakhir dari Nomor Akhir harus lebih besar dari 8 angka terakhir "
"dari Nomor Awal"
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_l10n_id_efaktur_efaktur_range____last_update
msgid "Last Modified on"
msgstr "Dimodifikasi Terakhir pada"
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_l10n_id_efaktur_efaktur_range__write_uid
msgid "Last Updated by"
msgstr "Diperbarui Terakhir oleh"
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_l10n_id_efaktur_efaktur_range__write_date
msgid "Last Updated on"
msgstr "Diperbarui Terakhir pada"
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_l10n_id_efaktur_efaktur_range__max
msgid "Max"
msgstr "Maks"
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_l10n_id_efaktur_efaktur_range__min
msgid "Min"
msgstr "Min"
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_res_partner__l10n_id_nik
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_res_users__l10n_id_nik
msgid "NIK"
msgstr "NIK"
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_bank_statement_line__l10n_id_replace_invoice_id
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_move__l10n_id_replace_invoice_id
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_payment__l10n_id_replace_invoice_id
msgid "Replace Invoice"
msgstr "Ganti Faktur"
#. module: l10n_id_efaktur
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid ""
"Replacement invoice only for invoices on which the e-Faktur is generated. "
msgstr "Faktur pengganti hanya untuk faktur yang dibuat oleh e-Faktur. "
#. module: l10n_id_efaktur
#: model_terms:ir.ui.view,arch_db:l10n_id_efaktur.account_move_efaktur_form_view
msgid "Reset E-Faktur"
msgstr "Reset E-Faktur"
#. module: l10n_id_efaktur
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid "Some documents are not Customer Invoices"
msgstr "Beberapa dokumen bukan merupakan Faktur Pelanggan"
#. module: l10n_id_efaktur
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid "Some documents don't have a transaction code"
msgstr "Beberapa dokumen tidak memiliki kode transaksi"
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_res_config_settings__l10n_id_tax_address
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_res_partner__l10n_id_tax_address
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_res_users__l10n_id_tax_address
msgid "Tax Address"
msgstr "Alamat Pajak"
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_res_config_settings__l10n_id_tax_name
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_res_partner__l10n_id_tax_name
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_res_users__l10n_id_tax_name
msgid "Tax Name"
msgstr "Nama Pajak"
#. module: l10n_id_efaktur
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_bank_statement_line__l10n_id_tax_number
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_move__l10n_id_tax_number
#: model:ir.model.fields,field_description:l10n_id_efaktur.field_account_payment__l10n_id_tax_number
msgid "Tax Number"
msgstr "Nomor Pajak"
#. module: l10n_id_efaktur
#: code:addons/l10n_id_efaktur/models/efaktur.py:0
#, python-format
msgid "The difference between the two numbers must not be greater than 10.000"
msgstr "Perbedaan antara dua nomor tidak boleh lebih besar dari 10.000"
#. module: l10n_id_efaktur
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid "The third digit of a tax number must be 0 or 1"
msgstr "Angka ketiga dari nomor pajak harus 0 atau 1"
#. module: l10n_id_efaktur
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid ""
"There is no Efaktur number available. Please configure the range you get "
"from the government in the e-Faktur menu. "
msgstr ""
"Tidak ada nomor Efaktur yang tersedia. Silakan konfigurasi rentang yang "
"Anda dapat dari pemerintah di menu e-Faktur. "
#. module: l10n_id_efaktur
#: code:addons/l10n_id_efaktur/models/efaktur.py:0
#, python-format
msgid "There should be 13 digits in each number."
msgstr "Harus ada 13 angka di setiap nomor."
#. module: l10n_id_efaktur
#: model_terms:ir.ui.view,arch_db:l10n_id_efaktur.view_account_invoice_filter
msgid "To Generate e-Faktur"
msgstr "Untuk Membuat e-Faktur"
#. module: l10n_id_efaktur
#: model_terms:ir.ui.view,arch_db:l10n_id_efaktur.efaktur_tree_view
msgid "Total Available"
msgstr "Total Tersedia"
#. module: l10n_id_efaktur
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid ""
"You can only change the number manually for a Vendor Bills and Credit Notes"
msgstr ""
"Anda hanya dapat mengubah nomor secara manual untuk Tagihan Vendor dan Nota "
"Kredit"
#. module: l10n_id_efaktur
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid "You have already generated the tax report for this document: %s"
msgstr "Anda sudah membuat laporan pajak untuk dokumen berikut: %s"
#. module: l10n_id_efaktur
#: code:addons/l10n_id_efaktur/models/account_move.py:0
#, python-format
msgid "You need to put a Kode Transaksi for this partner."
msgstr "Anda harus mengisi Kode Transaksi untuk partner ini."
#. module: l10n_id_efaktur
#: model:ir.actions.act_window,name:l10n_id_efaktur.efaktur_invoice_action
#: model:ir.ui.menu,name:l10n_id_efaktur.menu_efaktur_action
msgid "e-Faktur"
msgstr "e-Faktur"

View file

@ -0,0 +1,6 @@
# -*- encoding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import efaktur
from . import account_move
from . import res_partner

View file

@ -0,0 +1,371 @@
# -*- encoding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
import re
from odoo import api, fields, models, _
from odoo.exceptions import UserError, ValidationError, RedirectWarning
from odoo.tools import float_round, float_repr
FK_HEAD_LIST = ['FK', 'KD_JENIS_TRANSAKSI', 'FG_PENGGANTI', 'NOMOR_FAKTUR', 'MASA_PAJAK', 'TAHUN_PAJAK', 'TANGGAL_FAKTUR', 'NPWP', 'NAMA', 'ALAMAT_LENGKAP', 'JUMLAH_DPP', 'JUMLAH_PPN', 'JUMLAH_PPNBM', 'ID_KETERANGAN_TAMBAHAN', 'FG_UANG_MUKA', 'UANG_MUKA_DPP', 'UANG_MUKA_PPN', 'UANG_MUKA_PPNBM', 'REFERENSI', 'KODE_DOKUMEN_PENDUKUNG']
LT_HEAD_LIST = ['LT', 'NPWP', 'NAMA', 'JALAN', 'BLOK', 'NOMOR', 'RT', 'RW', 'KECAMATAN', 'KELURAHAN', 'KABUPATEN', 'PROPINSI', 'KODE_POS', 'NOMOR_TELEPON']
OF_HEAD_LIST = ['OF', 'KODE_OBJEK', 'NAMA', 'HARGA_SATUAN', 'JUMLAH_BARANG', 'HARGA_TOTAL', 'DISKON', 'DPP', 'PPN', 'TARIF_PPNBM', 'PPNBM']
def _csv_row(data, delimiter=',', quote='"'):
return quote + (quote + delimiter + quote).join([str(x).replace(quote, '\\' + quote) for x in data]) + quote + '\n'
class AccountMove(models.Model):
_inherit = "account.move"
l10n_id_tax_number = fields.Char(string="Tax Number", copy=False)
l10n_id_replace_invoice_id = fields.Many2one('account.move', string="Replace Invoice", domain="['|', '&', '&', ('state', '=', 'posted'), ('partner_id', '=', partner_id), ('reversal_move_id', '!=', False), ('state', '=', 'cancel')]", copy=False, index='btree_not_null')
l10n_id_attachment_id = fields.Many2one('ir.attachment', readonly=True, copy=False)
l10n_id_csv_created = fields.Boolean('CSV Created', compute='_compute_csv_created', copy=False)
l10n_id_kode_transaksi = fields.Selection([
('01', '01 To the Parties that is not VAT Collector (Regular Customers)'),
('02', '02 To the Treasurer'),
('03', '03 To other VAT Collectors other than the Treasurer'),
('04', '04 Other Value of VAT Imposition Base'),
('05', '05 Specified Amount (Article 9A Paragraph (1) VAT Law)'),
('06', '06 to individuals holding foreign passports'),
('07', '07 Deliveries that the VAT is not Collected'),
('08', '08 Deliveries that the VAT is Exempted'),
('09', '09 Deliveries of Assets (Article 16D of VAT Law)'),
], string='Kode Transaksi', help='Dua digit pertama nomor pajak',
readonly=False, states={'posted': [('readonly', True)], 'cancel': [('readonly', True)]}, copy=False,
compute="_compute_kode_transaksi", store=True)
l10n_id_need_kode_transaksi = fields.Boolean(compute='_compute_need_kode_transaksi')
@api.onchange('l10n_id_tax_number')
def _onchange_l10n_id_tax_number(self):
for record in self:
if record.l10n_id_tax_number and record.move_type not in self.get_purchase_types():
raise UserError(_("You can only change the number manually for a Vendor Bills and Credit Notes"))
@api.depends('l10n_id_attachment_id')
def _compute_csv_created(self):
for record in self:
record.l10n_id_csv_created = bool(record.l10n_id_attachment_id)
@api.depends('partner_id')
def _compute_kode_transaksi(self):
for move in self:
move.l10n_id_kode_transaksi = move.partner_id.commercial_partner_id.l10n_id_kode_transaksi
@api.depends('partner_id', 'line_ids.tax_ids')
def _compute_need_kode_transaksi(self):
for move in self:
# If there are no taxes at all on every line (0% taxes counts as having a tax) then we don't need a kode transaksi
move.l10n_id_need_kode_transaksi = (
move.partner_id.commercial_partner_id.l10n_id_pkp
and not move.l10n_id_tax_number
and move.move_type == 'out_invoice'
and move.country_code == 'ID'
and move.line_ids.tax_ids
)
@api.constrains('l10n_id_kode_transaksi', 'line_ids', 'partner_id')
def _constraint_kode_ppn(self):
ppn_tag = self.env.ref('l10n_id.ppn_tag')
for move in self.filtered(lambda m: m.l10n_id_need_kode_transaksi and m.l10n_id_kode_transaksi != '08'):
if any(ppn_tag.id in line.tax_tag_ids.ids for line in move.line_ids if line.display_type == 'product') \
and any(ppn_tag.id not in line.tax_tag_ids.ids for line in move.line_ids if line.display_type == 'product'):
raise UserError(_('Cannot mix VAT subject and Non-VAT subject items in the same invoice with this kode transaksi.'))
for move in self.filtered(lambda m: m.l10n_id_need_kode_transaksi and m.l10n_id_kode_transaksi == '08'):
if any(ppn_tag.id in line.tax_tag_ids.ids for line in move.line_ids if line.display_type == 'product'):
raise UserError('Kode transaksi 08 is only for non VAT subject items.')
@api.constrains('l10n_id_tax_number')
def _constrains_l10n_id_tax_number(self):
for record in self.filtered('l10n_id_tax_number'):
if record.l10n_id_tax_number != re.sub(r'\D', '', record.l10n_id_tax_number):
record.l10n_id_tax_number = re.sub(r'\D', '', record.l10n_id_tax_number)
if len(record.l10n_id_tax_number) != 16:
raise UserError(_('A tax number should have 16 digits'))
elif record.l10n_id_tax_number[:2] not in dict(self._fields['l10n_id_kode_transaksi'].selection).keys():
raise UserError(_('A tax number must begin by a valid Kode Transaksi'))
elif record.l10n_id_tax_number[2] not in ('0', '1'):
raise UserError(_('The third digit of a tax number must be 0 or 1'))
def _post(self, soft=True):
"""Set E-Faktur number after validation."""
for move in self:
if move.l10n_id_need_kode_transaksi:
if not move.l10n_id_kode_transaksi:
raise ValidationError(_('You need to put a Kode Transaksi for this partner.'))
if move.l10n_id_replace_invoice_id.l10n_id_tax_number:
if not move.l10n_id_replace_invoice_id.l10n_id_attachment_id:
raise ValidationError(_('Replacement invoice only for invoices on which the e-Faktur is generated. '))
rep_efaktur_str = move.l10n_id_replace_invoice_id.l10n_id_tax_number
move.l10n_id_tax_number = '%s1%s' % (move.l10n_id_kode_transaksi, rep_efaktur_str[3:])
else:
efaktur = self.env['l10n_id_efaktur.efaktur.range'].pop_number(move.company_id.id)
if not efaktur:
raise ValidationError(_('There is no Efaktur number available. Please configure the range you get from the government in the e-Faktur menu. '))
move.l10n_id_tax_number = '%s0%013d' % (str(move.l10n_id_kode_transaksi), efaktur)
return super()._post(soft)
def reset_efaktur(self):
"""Reset E-Faktur, so it can be use for other invoice."""
for move in self:
if move.l10n_id_attachment_id:
raise UserError(_('You have already generated the tax report for this document: %s', move.name))
self.env['l10n_id_efaktur.efaktur.range'].push_number(move.company_id.id, move.l10n_id_tax_number[3:])
move.message_post(
body='e-Faktur Reset: %s ' % (move.l10n_id_tax_number),
subject="Reset Efaktur")
move.l10n_id_tax_number = False
return True
def download_csv(self):
action = {
'type': 'ir.actions.act_url',
'url': "web/content/?model=ir.attachment&id=" + str(self.l10n_id_attachment_id.id) + "&filename_field=name&field=datas&download=true&name=" + self.l10n_id_attachment_id.name,
'target': 'self'
}
return action
def download_efaktur(self):
"""Collect the data and execute function _generate_efaktur."""
for record in self:
if record.state == 'draft':
raise ValidationError(_('Could not download E-faktur in draft state'))
if not record.l10n_id_tax_number:
if not record.l10n_id_need_kode_transaksi:
raise ValidationError(_('E-faktur is not available for invoices without any taxes.'))
raise ValidationError(_('Connect %(move_number)s with E-faktur to download this report', move_number=record.name))
self._generate_efaktur(',')
return self.download_csv()
def _generate_efaktur_invoice(self, delimiter):
"""Generate E-Faktur for customer invoice."""
# Invoice of Customer
output_head = '%s%s%s' % (
_csv_row(FK_HEAD_LIST, delimiter),
_csv_row(LT_HEAD_LIST, delimiter),
_csv_row(OF_HEAD_LIST, delimiter),
)
idr = self.env.ref('base.IDR')
for move in self.filtered(lambda m: m.state == 'posted'):
eTax = move._prepare_etax()
commercial_partner = move.partner_id.commercial_partner_id
nik = str(commercial_partner.l10n_id_nik) if not commercial_partner.vat else ''
if move.l10n_id_replace_invoice_id:
number_ref = str(move.l10n_id_replace_invoice_id.name) + " replaced by " + str(move.name) + " " + nik
elif nik:
number_ref = str(move.name) + " " + nik
else:
number_ref = str(move.name)
street = ', '.join([x for x in (move.partner_id.street, move.partner_id.street2) if x])
invoice_npwp = ''
if commercial_partner.vat and len(commercial_partner.vat) >= 15:
invoice_npwp = commercial_partner.vat
elif commercial_partner.l10n_id_nik:
invoice_npwp = commercial_partner.l10n_id_nik
if not invoice_npwp:
action_error = {
'view_mode': 'form',
'res_model': 'res.partner',
'type': 'ir.actions.act_window',
'res_id': commercial_partner.id,
'views': [[self.env.ref('base.view_partner_form').id, 'form']],
}
msg = _("Please make sure that you've input the appropriate NPWP or NIK for the following customer")
raise RedirectWarning(msg, action_error, _("Edit Customer Information"))
invoice_npwp = invoice_npwp.replace('.', '').replace('-', '')
etax_name = commercial_partner.l10n_id_tax_name or move.partner_id.name
if invoice_npwp[:15] == '000000000000000' and commercial_partner.l10n_id_nik:
etax_name = "%s#NIK#NAMA#%s" % (commercial_partner.l10n_id_nik, etax_name)
# Here all fields or columns based on eTax Invoice Third Party
eTax['KD_JENIS_TRANSAKSI'] = move.l10n_id_tax_number[0:2] or 0
eTax['FG_PENGGANTI'] = move.l10n_id_tax_number[2:3] or 0
eTax['NOMOR_FAKTUR'] = move.l10n_id_tax_number[3:] or 0
eTax['MASA_PAJAK'] = move.invoice_date.month
eTax['TAHUN_PAJAK'] = move.invoice_date.year
eTax['TANGGAL_FAKTUR'] = '{0}/{1}/{2}'.format(move.invoice_date.day, move.invoice_date.month, move.invoice_date.year)
eTax['NPWP'] = invoice_npwp
eTax['NAMA'] = etax_name
eTax['ALAMAT_LENGKAP'] = move.partner_id.contact_address.replace('\n', '').strip() if eTax['NPWP'] == '000000000000000' else commercial_partner.l10n_id_tax_address or street
eTax['JUMLAH_DPP'] = int(float_round(move.amount_untaxed, 0)) # currency rounded to the unit
eTax['JUMLAH_PPN'] = int(float_round(move.amount_tax, 0, rounding_method="DOWN")) # tax amount ALWAYS rounded down
eTax['ID_KETERANGAN_TAMBAHAN'] = '1' if move.l10n_id_kode_transaksi == '07' else ''
eTax['REFERENSI'] = number_ref
eTax['KODE_DOKUMEN_PENDUKUNG'] = '0'
lines = move.line_ids.filtered(lambda x: x.move_id._is_downpayment() and x.price_unit < 0 and x.display_type == 'product')
eTax['FG_UANG_MUKA'] = 0
eTax['UANG_MUKA_DPP'] = float_repr(abs(sum(lines.mapped(lambda l: float_round(l.price_subtotal, 0)))), 0)
eTax['UANG_MUKA_PPN'] = float_repr(abs(sum(lines.mapped(lambda l: float_round(l.price_total - l.price_subtotal, 0)))), 0)
fk_values_list = ['FK'] + [eTax[f] for f in FK_HEAD_LIST[1:]]
# HOW TO ADD 2 line to 1 line for free product
free, sales = [], []
for line in move.line_ids.filtered(lambda l: l.display_type == 'product'):
# *invoice_line_unit_price is price unit use for harga_satuan's column
# *invoice_line_quantity is quantity use for jumlah_barang's column
# *invoice_line_total_price is bruto price use for harga_total's column
# *invoice_line_discount_m2m is discount price use for diskon's column
# *line.price_subtotal is subtotal price use for dpp's column
# *tax_line or free_tax_line is tax price use for ppn's column
free_tax_line = tax_line = 0.0
for tax in line.tax_ids:
if tax.amount > 0:
tax_line += line.price_subtotal * (tax.amount / 100.0)
discount = 1 - (line.discount / 100)
# guarantees price to be tax-excluded
invoice_line_total_price = line.price_subtotal / discount if discount else 0
invoice_line_unit_price = invoice_line_total_price / line.quantity if line.quantity else 0
line_dict = {
'KODE_OBJEK': line.product_id.default_code or '',
'NAMA': line.product_id.name or '',
'HARGA_SATUAN': float_repr(idr.round(invoice_line_unit_price), idr.decimal_places),
'JUMLAH_BARANG': line.quantity,
'HARGA_TOTAL': idr.round(invoice_line_total_price),
'DPP': line.price_subtotal,
'product_id': line.product_id.id,
}
if line.price_subtotal < 0:
for tax in line.tax_ids:
free_tax_line += (line.price_subtotal * (tax.amount / 100.0)) * -1.0
line_dict.update({
'DISKON': float_round(invoice_line_total_price - line.price_subtotal, 0),
'PPN': free_tax_line,
})
free.append(line_dict)
elif line.price_subtotal != 0.0:
invoice_line_discount_m2m = invoice_line_total_price - line.price_subtotal
line_dict.update({
'DISKON': float_round(invoice_line_discount_m2m, 0),
'PPN': tax_line,
})
sales.append(line_dict)
sub_total_before_adjustment = sub_total_ppn_before_adjustment = 0.0
# We are finding the product that has affected
# by free product to adjustment the calculation
# of discount and subtotal.
# - the price total of free product will be
# included as a discount to related of product.
for sale in sales:
for f in free:
if f['product_id'] == sale['product_id']:
sale['DISKON'] = sale['DISKON'] - f['DISKON'] + f['PPN']
sale['DPP'] = sale['DPP'] + f['DPP']
tax_line = 0
for tax in line.tax_ids:
if tax.amount > 0:
tax_line += sale['DPP'] * (tax.amount / 100.0)
sale['PPN'] = tax_line
free.remove(f)
sub_total_before_adjustment += sale['DPP']
sub_total_ppn_before_adjustment += sale['PPN']
sale.update({
# Use the db currency rounding to float_round the DPP/PPN.
# As we will correct them we need them to be close to the final result.
'DPP': idr.round(sale['DPP']),
'PPN': idr.round(sale['PPN']),
'DISKON': float_repr(sale['DISKON'], 0),
})
# The total of the base (DPP) and taxes (PPN) must be a integer, equal to the JUMLAH_DPP and JUMLAH_PPN
# To do so, we adjust the first line in order to achieve the correct total
if sales:
diff_dpp = idr.round(eTax['JUMLAH_DPP'] - sum([sale['DPP'] for sale in sales]))
total_sales_ppn = idr.round(eTax['JUMLAH_PPN'] - sum([sale['PPN'] for sale in sales]))
# We will add the differences to the first line for which adding the difference will not result in a negative value.
for sale in sales:
if sale['DPP'] + diff_dpp >= 0 and sale['PPN'] + total_sales_ppn >= 0:
sale['HARGA_TOTAL'] += diff_dpp
sale['DPP'] += diff_dpp
diff_dpp = 0
sale['PPN'] += total_sales_ppn
total_sales_ppn = 0
break
# We couldn't adjust everything in a single line as their values is too low.
# So we will instead slit the adjustment in multiple lines.
if diff_dpp or total_sales_ppn:
for sale in sales:
# DPP
sale_dpp = sale['DPP']
sale["DPP"] = max(0, sale["DPP"] + diff_dpp)
diff_dpp -= (sale["DPP"] - sale_dpp)
sale['HARGA_TOTAL'] = sale["DPP"]
# PPN
sale_ppn = sale['PPN']
sale["PPN"] = max(0, sale["PPN"] + total_sales_ppn)
total_sales_ppn -= (sale["PPN"] - sale_ppn)
# Values now being corrected, we can format them for the CSV
for sale in sales:
sale.update({
'HARGA_TOTAL': float_repr(sale['HARGA_TOTAL'], idr.decimal_places),
'DPP': float_repr(sale['DPP'], idr.decimal_places),
'PPN': float_repr(sale['PPN'], idr.decimal_places),
})
output_head += _csv_row(fk_values_list, delimiter)
for sale in sales:
of_values_list = ['OF'] + [str(sale[f]) for f in OF_HEAD_LIST[1:-2]] + ['0', '0']
output_head += _csv_row(of_values_list, delimiter)
return output_head
def _prepare_etax(self):
# These values are never set
return {'JUMLAH_PPNBM': 0, 'UANG_MUKA_PPNBM': 0, 'JUMLAH_BARANG': 0, 'TARIF_PPNBM': 0, 'PPNBM': 0}
def _generate_efaktur(self, delimiter):
if self.filtered(lambda x: not x.l10n_id_kode_transaksi):
raise UserError(_('Some documents don\'t have a transaction code'))
if self.filtered(lambda x: x.move_type != 'out_invoice'):
raise UserError(_('Some documents are not Customer Invoices'))
output_head = self._generate_efaktur_invoice(delimiter)
my_utf8 = output_head.encode("utf-8")
out = base64.b64encode(my_utf8)
attachment = self.env['ir.attachment'].create({
'datas': out,
'name': 'efaktur_%s.csv' % (fields.Datetime.to_string(fields.Datetime.now()).replace(" ", "_")),
'type': 'binary',
})
for record in self:
record.message_post(attachment_ids=[attachment.id])
self.l10n_id_attachment_id = attachment.id
return {
'type': 'ir.actions.client',
'tag': 'reload',
}

View file

@ -0,0 +1,111 @@
# -*- encoding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
import re
class Efaktur(models.Model):
_name = "l10n_id_efaktur.efaktur.range"
_description = "Available E-faktur range"
company_id = fields.Many2one('res.company', required=True, default=lambda self: self.env.company)
max = fields.Char(compute='_compute_default', store=True, readonly=False)
min = fields.Char(compute='_compute_default', store=True, readonly=False)
available = fields.Integer(compute='_compute_available', store=True)
@api.model
def pop_number(self, company_id):
range = self.search([('company_id', '=', company_id)], order="min ASC", limit=1)
if not range:
return None
popped = int(range.min)
if int(range.min) >= int(range.max):
range.unlink()
else:
range.min = '%013d' % (popped + 1)
return popped
@api.model
def push_number(self, company_id, number):
return self.push_numbers(company_id, number, number)
@api.model
def push_numbers(self, company_id, min, max):
range_sup = self.search([('min', '=', '%013d' % (int(max) + 1))])
if range_sup:
range_sup.min = '%013d' % int(min)
max = range_sup.max
range_low = self.search([('max', '=', '%013d' % (int(max) - 1))])
if range_low:
range_sup.unlink()
range_low.max = '%013d' % int(max)
if not range_sup and not range_low:
self.create({
'company_id': company_id,
'max': '%013d' % int(max),
'min': '%013d' % int(min),
})
@api.constrains('min', 'max')
def _constrains_min_max(self):
for record in self:
if not len(record.min) == 13 or not len(record.max) == 13:
raise ValidationError(_("There should be 13 digits in each number."))
if record.min[:-8] != record.max[:-8]:
raise ValidationError(_("First 5 digits should be same in Start Number and End Number."))
if int(record.min[-8:]) > int(record.max[-8:]):
raise ValidationError(_("Last 8 digits of End Number should be greater than the last 8 digit of Start Number"))
if (int(record.max) - int(record.min)) > 10000:
raise ValidationError(_("The difference between the two numbers must not be greater than 10.000"))
# The number of records should always be very small, so it is ok to search in loop
if self.search([
'&', ('id', '!=', record.id), '|', '|',
'&', ('min', '<=', record.max), ('max', '>=', record.max),
'&', ('min', '<=', record.min), ('max', '>=', record.min),
'&', ('min', '>=', record.min), ('max', '<=', record.max),
]):
raise ValidationError(_('Efaktur interleaving range detected'))
@api.depends('min', 'max')
def _compute_available(self):
for record in self:
record.available = 1 + int(record.max) - int(record.min)
@api.depends('company_id')
def _compute_default(self):
for record in self:
query = """
SELECT MAX(SUBSTRING(l10n_id_tax_number FROM 4))
FROM account_move
WHERE l10n_id_tax_number IS NOT NULL
AND company_id = %s
"""
self.env.cr.execute(query, [record.company_id.id])
max_used = int(self.env.cr.fetchone()[0] or 0)
max_available = int(self.env['l10n_id_efaktur.efaktur.range'].search([('company_id', '=', record.company_id.id)], order='max DESC', limit=1).max)
record.min = record.max = '%013d' % (max(max_available, max_used) + 1)
@api.onchange('min')
def _onchange_min(self):
min_val = re.sub(r'\D', '', str(self.min)) or 0
self.min = '%013d' % int(min_val)
if not self.max or int(self.min) > int(self.max):
self.max = self.min
@api.onchange('max')
def _onchange_max(self):
max_val = re.sub(r'\D', '', str(self.max)) or 0
self.max = '%013d' % int(max_val)
if not self.min or int(self.min) > int(self.max):
self.min = self.max

View file

@ -0,0 +1,41 @@
# -*- encoding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, api
class ResPartner(models.Model):
"""Inherit res.partner object to add NPWP field and Kode Transaksi"""
_inherit = "res.partner"
l10n_id_pkp = fields.Boolean(string="ID PKP", compute='_compute_l10n_id_pkp', store=True, readonly=False)
l10n_id_nik = fields.Char(string='NIK')
l10n_id_tax_address = fields.Char('Tax Address')
l10n_id_tax_name = fields.Char('Tax Name')
l10n_id_kode_transaksi = fields.Selection([
('01', '01 To the Parties that is not VAT Collector (Regular Customers)'),
('02', '02 To the Treasurer'),
('03', '03 To other VAT Collectors other than the Treasurer'),
('04', '04 Other Value of VAT Imposition Base'),
('05', '05 Specified Amount (Article 9A Paragraph (1) VAT Law)'),
('06', '06 to individuals holding foreign passports'),
('07', '07 Deliveries that the VAT is not Collected'),
('08', '08 Deliveries that the VAT is Exempted'),
('09', '09 Deliveries of Assets (Article 16D of VAT Law)'),
],
string='Kode Transaksi',
help='Dua digit pertama nomor pajak',
default='01',
)
@api.depends('vat', 'country_code')
def _compute_l10n_id_pkp(self):
for record in self:
record.l10n_id_pkp = record.vat and record.country_code == 'ID'
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
l10n_id_tax_address = fields.Char('Tax Address', related='company_id.partner_id.l10n_id_tax_address', readonly=False)
l10n_id_tax_name = fields.Char('Tax Name', related='company_id.partner_id.l10n_id_tax_address', readonly=False)

View file

@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_efaktur_user,access.efaktur.user,model_l10n_id_efaktur_efaktur_range,account.group_account_invoice,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_efaktur_user access.efaktur.user model_l10n_id_efaktur_efaktur_range account.group_account_invoice 1 1 1 1

View file

@ -0,0 +1 @@
from . import test_l10n_id_efaktur

View file

@ -0,0 +1,291 @@
import csv
from odoo import Command
from odoo.exceptions import ValidationError
from odoo.tests import tagged
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.addons.l10n_id_efaktur.models.account_move import FK_HEAD_LIST, LT_HEAD_LIST, OF_HEAD_LIST, _csv_row, AccountMove
from odoo.exceptions import RedirectWarning
from unittest.mock import patch
@tagged('post_install', '-at_install', 'post_install_l10n')
class TestIndonesianEfaktur(AccountTestInvoicingCommon):
@classmethod
def setUpClass(cls, chart_template_ref='l10n_id.l10n_id_chart'):
super().setUpClass(chart_template_ref=chart_template_ref)
"""
1) contact with l10n_id_pkp=True, l10n_id_kode_transaksi="01"
2) tax: amount=10, type_tax_use=sale, price_include=True
3) invoice with partner_id=contact, journal=customer invoices,
"""
cls.company_data['company'].write({
'street': 'test',
'phone': '12345',
'vat': '1234567890123456',
})
# For the sake of unit test of this module, we want to retain the the compute method for field
# l10n_id_need_kode_transaksi of this module. In the coretax module, l10n_id_need_kode_transaksi
# is always set to False to prevent the flows of old module to be triggered
patch_kode_transaksi = patch('odoo.addons.l10n_id_efaktur_coretax.models.account_move.AccountMove._compute_need_kode_transaksi',
AccountMove._compute_need_kode_transaksi)
cls.startClassPatcher(patch_kode_transaksi)
indonesia = cls.env.ref('base.id')
cls.partner_id = cls.env['res.partner'].create({"name": "l10ntest", "l10n_id_pkp": True, "l10n_id_kode_transaksi": "01", "l10n_id_nik": "12345", "vat": "000000000000000", "country_id": indonesia.id})
cls.partner_id_vat = cls.env['res.partner'].create({"name": "l10ntest3", "l10n_id_pkp": True, "l10n_id_kode_transaksi": "01", "l10n_id_nik": "67890", "vat": "010000000000000"})
cls.tax_id = cls.env['account.tax'].create({"name": "test tax", "type_tax_use": "sale", "amount": 10.0, "price_include": True})
cls.efaktur = cls.env['l10n_id_efaktur.efaktur.range'].create({'min': '0000000000001', 'max': '0000000000010'})
cls.out_invoice_1 = cls.env['account.move'].create({
'move_type': 'out_invoice',
'partner_id': cls.partner_id.id,
'invoice_date': '2019-05-01',
'date': '2019-05-01',
'invoice_line_ids': [
(0, 0, {'name': 'line1', 'price_unit': 110.0, 'tax_ids': cls.tax_id.ids}),
],
'l10n_id_kode_transaksi': "01",
})
cls.out_invoice_1.action_post()
cls.out_invoice_2 = cls.env['account.move'].create({
'move_type': 'out_invoice',
'partner_id': cls.partner_id.id,
'invoice_date': '2019-05-01',
'date': '2019-05-01',
'invoice_line_ids': [
(0, 0, {'name': 'line1', 'price_unit': 110.11, 'quantity': 400, 'tax_ids': cls.tax_id.ids})
],
'l10n_id_kode_transaksi': '01'
})
cls.out_invoice_2.action_post()
def test_efaktur_csv_output_1(self):
"""
Test to ensure that the output csv data contains tax-excluded prices regardless of whether the tax configuration is tax-included or tax-excluded.
Current test is using price of 110 which is tax-included with tax of amount 10%. So the unit price listed has to be 100 whereas the original result would have 110 instead.
"""
# to check the diff when test fails
efaktur_csv_output = self.out_invoice_1._generate_efaktur_invoice(',')
output_head = '%s%s%s' % (
_csv_row(FK_HEAD_LIST, ','),
_csv_row(LT_HEAD_LIST, ','),
_csv_row(OF_HEAD_LIST, ','),
)
# remaining lines
line_4 = '"FK","01","0","0000000000001","5","2019","1/5/2019","000000000000000","12345#NIK#NAMA#l10ntest","Indonesia","100","10","0","","0","0","0","0","INV/2019/00001","0"\n'
line_5 = '"OF","","","100.00","1.0","100.00","0","100.00","10.00","0","0"\n'
efaktur_csv_expected = output_head + line_4 + line_5
self.assertEqual(efaktur_csv_expected, efaktur_csv_output)
def test_efaktur_csv_output_decimal_place(self):
"""
Test to ensure that decimal place conversion is only done when inputting to csv
This is to test original calculation of invoice_line_total_price: invoice_line_total_price = invoice_line_unit_price * line.quantity
as invoice_line_unit_price is already converted to be tax-excluded and set to the decimal place as configured on the currency, the calculation of total could be flawed.
In this test case, the tax-included price unit is 110.11, hence tax-excluded is 100.1,
invoice_line_unit_price will be 100, if we continue with the calculation of total price, it will be 100*400 = 40000
eventhough the total is supposed to be 100.1*400 = 40040, there is a 40 discrepancy
"""
efaktur_csv_output = self.out_invoice_2._generate_efaktur_invoice(',')
output_head = '%s%s%s' % (
_csv_row(FK_HEAD_LIST, ','),
_csv_row(LT_HEAD_LIST, ','),
_csv_row(OF_HEAD_LIST, ','),
)
line_4 = '"FK","01","0","0000000000002","5","2019","1/5/2019","000000000000000","12345#NIK#NAMA#l10ntest","Indonesia","40040","4004","0","","0","0","0","0","INV/2019/00002","0"\n'
line_5 = '"OF","","","100.10","400.0","40040.00","0","40040.00","4004.00","0","0"\n'
efaktur_csv_expected = output_head + line_4 + line_5
self.assertEqual(efaktur_csv_expected, efaktur_csv_output)
def test_efaktur_use_vat(self):
""" Test to ensure that the e-faktur uses the VAT on NPWP column of efaktur when
VAT is non-zeros """
out_invoice = self.env['account.move'].create({
'move_type': 'out_invoice',
'partner_id': self.partner_id_vat.id,
'invoice_date': '2019-05-01',
'date': '2019-05-01',
'invoice_line_ids': [
(0, 0, {'name': 'line1', 'price_unit': 110.11, 'quantity': 400, 'tax_ids': self.tax_id.ids})
],
'l10n_id_kode_transaksi': '01'
})
out_invoice.action_post()
efaktur_csv_output = out_invoice._generate_efaktur_invoice(',')
output_head = '%s%s%s' % (
_csv_row(FK_HEAD_LIST, ','),
_csv_row(LT_HEAD_LIST, ','),
_csv_row(OF_HEAD_LIST, ','),
)
line_4 = '"FK","01","0","0000000000003","5","2019","1/5/2019","010000000000000","l10ntest3","","40040","4004","0","","0","0","0","0","INV/2019/00003","0"\n'
line_5 = '"OF","","","100.10","400.0","40040.00","0","40040.00","4004.00","0","0"\n'
efaktur_csv_expected = output_head + line_4 + line_5
self.assertEqual(efaktur_csv_expected, efaktur_csv_output)
def test_efaktur_no_vat_nik(self):
""" Test to ensure that when no VAT and NIK is supplied, a RedirectWarning should be raised """
partner_no_vat_nik = self.env['res.partner'].create({"name": "l10ntest4", "l10n_id_pkp": True, "l10n_id_kode_transaksi": "01"})
out_invoice = self.env['account.move'].create({
'move_type': 'out_invoice',
'partner_id': partner_no_vat_nik.id,
'invoice_date': '2019-05-01',
'date': '2019-05-01',
'invoice_line_ids': [
(0, 0, {'name': 'line1', 'price_unit': 110.11, 'quantity': 400, 'tax_ids': self.tax_id.ids})
],
'l10n_id_kode_transaksi': '01'
})
out_invoice.action_post()
with self.assertRaises(RedirectWarning):
out_invoice._generate_efaktur_invoice(',')
def test_efaktur_nik_with_no_vat(self):
""" Test to ensure if there is contact has no VAT but has NIK
NPWP would contain NIK, NAMA contains customer's name, REFERENSI would contain invoice name with customer's NIK"""
partner_nik_no_vat = self.env['res.partner'].create({"name": "l10ntest4", "l10n_id_pkp": True, "l10n_id_kode_transaksi": "01", "l10n_id_nik": "1532167"})
out_invoice = self.env['account.move'].create({
'move_type': 'out_invoice',
'partner_id': partner_nik_no_vat.id,
'invoice_date': '2019-05-01',
'date': '2019-05-01',
'invoice_line_ids': [
(0, 0, {'name': 'line1', 'price_unit': 110.11, 'quantity': 400, 'tax_ids': self.tax_id.ids})
],
'l10n_id_kode_transaksi': '01'
})
out_invoice.action_post()
efaktur_csv_output = out_invoice._generate_efaktur_invoice(',')
output_head = '%s%s%s' % (
_csv_row(FK_HEAD_LIST, ','),
_csv_row(LT_HEAD_LIST, ','),
_csv_row(OF_HEAD_LIST, ','),
)
line_4 = '"FK","01","0","0000000000003","5","2019","1/5/2019","1532167","l10ntest4","","40040","4004","0","","0","0","0","0","INV/2019/00003 1532167","0"\n'
line_5 = '"OF","","","100.10","400.0","40040.00","0","40040.00","4004.00","0","0"\n'
efaktur_csv_expected = output_head + line_4 + line_5
self.assertEqual(efaktur_csv_expected, efaktur_csv_output)
def test_efaktur_total_rounding_accuracy(self):
""" Use case:
Using a tax of 11% price included on every line:
line | qty | price | subtotal
1 | 24 | 57851 | 1250832.43
2 | 24 | 65184 | 1409383.78
3 | 24 | 77134 | 1667762.16
4 | 24 | 87835 | 1899135.14
5 | 20 | 180342| 3249405.41
Untaxed Amount: 9474250.92
Taxes: 1042176.08
Total: 10518936.00
Efaktur will display both:
-The detail of the lines.
and
- the amount_untaxed and amount_tax rounded to 0 decimals.
The sum of the lines MUST exactly match with the amount_untaxed and amount_tax.
Which most of the case won't be happening because sum(rounded(vals)) is usually not equal to rounded(sum(vals))
when using integers.
To remediate to that issue we are putting the difference in the amount of the first line.
"""
# Prepare the test invoice.
tax_id = self.env["account.tax"].create(
{"name": "test tax 11", "type_tax_use": "sale", "amount": 11.0, "price_include": True}
)
invoice = self.env['account.move'].create({
'move_type': 'out_invoice',
'partner_id': self.partner_id.id,
'invoice_date': '2019-05-01',
'date': '2019-05-01',
'invoice_line_ids': [
Command.create({'name': 'line1', 'price_unit': 57851, 'quantity': 24, 'tax_ids': tax_id.ids}),
Command.create({'name': 'line2', 'price_unit': 65184, 'quantity': 24, 'tax_ids': tax_id.ids}),
Command.create({'name': 'line3', 'price_unit': 77134, 'quantity': 24, 'tax_ids': tax_id.ids}),
Command.create({'name': 'line4', 'price_unit': 87835, 'quantity': 24, 'tax_ids': tax_id.ids}),
Command.create({'name': 'line5', 'price_unit': 180342, 'quantity': 20, 'tax_ids': tax_id.ids}),
],
'l10n_id_kode_transaksi': '01',
})
invoice.action_post()
# Generate the generate efaktur csv.
efaktur_csv_output = invoice._generate_efaktur_invoice(',')
# Validate the result: the sum of the lines must exactly match with the amount_untaxed and amount_tax.
dict_reader = csv.DictReader(efaktur_csv_output.splitlines(), delimiter=",")
amount_untaxed_total = 0
amount_tax_total = 0
amount_untaxed_sum = 0
amount_tax_sum = 0
for row in dict_reader:
row_code = row["FK"]
# Besides the first row, FK is used for the total row.
if row_code == "FK":
amount_untaxed_total = int(row["JUMLAH_DPP"]) # These are rounded to 0 decimal in the file.
amount_tax_total = int(row["JUMLAH_PPN"]) # These are rounded to 0 decimal in the file.
# OF lines are the data lines, but also used in another case (for codes). Data lines do not have a KD_JENIS_TRANSAKSI though.
elif row_code == "OF" and not row["KD_JENIS_TRANSAKSI"]:
amount_untaxed_sum += float(row["NPWP"]) # These are not.
amount_tax_sum += float(row["NAMA"]) # These are not.
self.assertEqual(amount_untaxed_total, amount_untaxed_sum)
self.assertEqual(amount_tax_total, amount_tax_sum)
def test_efaktur_do_not_consume_code(self):
""" Ensure that an invoice with no taxes at all will not consume a code. """
available_code = self.efaktur.available
# 1. No taxes
out_invoice_no_taxes = self.env["account.move"].create({
"move_type": "out_invoice",
"partner_id": self.partner_id.id,
"invoice_date": "2019-05-01",
"date": "2019-05-01",
"invoice_line_ids": [
Command.create({"name": "line1", "price_unit": 110.0, "tax_ids": False}),
],
"l10n_id_kode_transaksi": "01",
})
out_invoice_no_taxes.action_post()
# The tax number is not set.
self.assertFalse(out_invoice_no_taxes.l10n_id_tax_number)
# No codes have been consumed.
self.assertEqual(self.efaktur.available, available_code)
with self.assertRaises(ValidationError, msg='E-faktur is not available for invoices without any taxes.'), self.cr.savepoint():
out_invoice_no_taxes.download_efaktur()
def test_efaktur_consume_code(self):
""" Ensure that an invoice with taxes will consume a code. """
available_code = self.efaktur.available
out_invoice_taxes = self.env["account.move"].create({
"move_type": "out_invoice",
"partner_id": self.partner_id.id,
"invoice_date": "2019-05-01",
"date": "2019-05-01",
"invoice_line_ids": [
Command.create({"name": "line1", "price_unit": 110.0, "tax_ids": self.tax_id.ids}),
],
"l10n_id_kode_transaksi": "01",
})
out_invoice_taxes.action_post()
# The tax number is set.
self.assertEqual(out_invoice_taxes.l10n_id_tax_number, '0100000000000003')
# A code has been consumed.
self.assertEqual(self.efaktur.available, available_code - 1)
# No error is raised when downloading.
out_invoice_taxes.download_efaktur()

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="account_move_efaktur_form_view" model="ir.ui.view">
<field name="name">account.move.efaktur.form.view</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_move_form"/>
<field name="arch" type="xml">
<field name="partner_id" position="after">
<field name="l10n_id_need_kode_transaksi" invisible="1"/>
<field name="l10n_id_attachment_id" invisible="1"/>
<field name="l10n_id_kode_transaksi" attrs="{'invisible': ['|', ('country_code', '!=', 'ID'), ('l10n_id_need_kode_transaksi', '=', False)], 'required': [('l10n_id_need_kode_transaksi', '=', True)]}"/>
<field name="l10n_id_replace_invoice_id" attrs="{'invisible': [('country_code', '!=', 'ID')], 'readonly': [('state', '!=','draft')]}" options="{'m2o_dialog': False, 'no_create': True}"/>
</field>
<button name="button_draft" position="after">
<button name="reset_efaktur" string="Reset E-Faktur" type="object" attrs="{'invisible':['|', ('country_code', '!=', 'ID'), '|', '|', ('l10n_id_tax_number','=',False), ('state','!=','cancel'), ('l10n_id_attachment_id','!=',False)]}"/>
</button>
<xpath expr=".//group[@id='other_tab_group']" position="inside">
<group string="Electronic Tax" attrs="{'invisible': [('country_code', '!=', 'ID')]}">
<field name="l10n_id_tax_number" attrs="{'invisible': [('move_type', '=', 'entry')], 'readonly': [('move_type', 'in', ('out_invoice', 'out_refund', 'out_receipt'))]}"/>
<field name="l10n_id_csv_created" attrs="{'invisible': [('move_type', 'not in', ('out_invoice', 'out_refund'))]}"/>
</group>
</xpath>
</field>
</record>
<record id="account_move_efaktur_tree_view" model="ir.ui.view">
<field name="name">account.move.efaktur.tree.view</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_invoice_tree"/>
<field name="arch" type="xml">
<field name="invoice_user_id" position="after">
<field name="l10n_id_tax_number" optional="show"/>
<field name="l10n_id_csv_created" optional="hide"/>
</field>
</field>
</record>
<record id="dowload_efaktur_action" model="ir.actions.server">
<field name="name">Download e-Faktur</field>
<field name="model_id" ref="account.model_account_move"/>
<field name="binding_model_id" ref="account.model_account_move"/>
<field name="state">code</field>
<field name="code">action = records.download_efaktur()</field>
</record>
<record id="view_account_invoice_filter" model="ir.ui.view">
<field name="name">account.move.select.l10n_id.inherit</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_account_invoice_filter"/>
<field name="arch" type="xml">
<field name="name" position="after">
<field name="l10n_id_tax_number"/>
<field name="l10n_id_attachment_id"/>
<group>
<filter string="To Generate e-Faktur" name="tax_csv_upload" domain="[('l10n_id_tax_number', '!=', False), ('l10n_id_attachment_id', '=', False), ('state', '=', 'posted')]" />
</group>
</field>
</field>
</record>
</data>
</odoo>

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="efaktur_tree_view" model="ir.ui.view">
<field name="name">l10n_id_efaktur.efaktur.range.tree.view</field>
<field name="model">l10n_id_efaktur.efaktur.range</field>
<field name="arch" type="xml">
<tree string="Efaktur Number" editable="bottom">
<field name="min"/>
<field name="max"/>
<field name="available" sum="Total Available"/>
<field name="company_id" groups="base.group_multi_company"/>
<field name="company_id" invisible="1" groups="!base.group_multi_company"/>
</tree>
</field>
</record>
<record id='efaktur_invoice_action' model='ir.actions.act_window'>
<field name="name">e-Faktur</field>
<field name="res_model">l10n_id_efaktur.efaktur.range</field>
<field name="view_mode">tree</field>
<field name="context">{'search_default_upload': True, 'search_default_used': True}</field>
<field name="view_id" ref="efaktur_tree_view"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
In order to be able to export customer invoices as e-Faktur
for the Indonesian government, you need to put here the ranges
of numbers you were assigned by the government.
When you validate an invoice, a number will be assigned based on these ranges.
Afterwards, you can filter the invoices still to export in the
invoices list and click on Action > Download e-Faktur
</p>
</field>
</record>
<menuitem id="menu_efaktur_action" name="e-Faktur"
parent="account.menu_finance_receivables"
groups="account.group_account_manager"
action="efaktur_invoice_action" sequence="111"/>
</data>
</odoo>

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.view.form.chilean.loc</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="account.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@id='invoicing_settings']" position="after">
<div attrs="{'invisible': [('country_code', '!=', 'ID')]}">
<div id="l10n_cl_title">
<h2>Indonesian Localization</h2>
</div>
<div id="l10n_cl_section" class="row mt16 o_settings_container">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane"/>
<div class="o_setting_right_pane">
<span class="fa fa-lg fa-building-o" title="Values set here are company-specific." aria-label="Values set here are company-specific." groups="base.group_multi_company" role="img"/>
<div class="content-group">
<div class="row mt16">
<label for="l10n_id_tax_address" class="col-lg-3"/>
<field name="l10n_id_tax_address"/>
</div>
<div class="row mt16">
<label for="l10n_id_tax_name" class="col-lg-3"/>
<field name="l10n_id_tax_name"/>
</div>
</div>
</div>
</div>
</div>
</div>
</xpath>
</field>
</record>
</odoo>

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="res_partner_tax_form_view" model="ir.ui.view">
<field name="name">res.partner.tax.form.view</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<xpath expr="//group/group" position="inside">
<field name="l10n_id_pkp" attrs="{'invisible': [('country_id', '!=', False), ('country_code', '!=', 'ID')]}"/>
<field name="l10n_id_kode_transaksi" attrs="{'invisible': [('l10n_id_pkp', '!=', True)]}"/>
</xpath>
<page name="accounting" position="inside">
<group string="Indonesian Taxes" attrs="{'invisible': [('l10n_id_pkp', '!=', True)]}">
<group>
<field name="l10n_id_nik"/>
</group>
<group>
<field name="l10n_id_tax_address"/>
<field name="l10n_id_tax_name"/>
</group>
</group>
</page>
</field>
</record>
</data>
</odoo>

View file

@ -0,0 +1,42 @@
[project]
name = "odoo-bringout-oca-ocb-l10n_id_efaktur"
version = "16.0.0"
description = "Indonesia E-faktur - Odoo addon"
authors = [
{ name = "Ernad Husremovic", email = "hernad@bring.out.ba" }
]
dependencies = [
"odoo-bringout-oca-ocb-l10n_id>=16.0.0",
"requests>=2.25.1"
]
readme = "README.md"
requires-python = ">= 3.11"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Office/Business",
]
[project.urls]
homepage = "https://github.com/bringout/0"
repository = "https://github.com/bringout/0"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[tool.hatch.metadata]
allow-direct-references = true
[tool.hatch.build.targets.wheel]
packages = ["l10n_id_efaktur"]
[tool.rye]
managed = true
dev-dependencies = [
"pytest>=8.4.1",
]