diff --git a/.copier-answers.yml b/.copier-answers.yml index f413811a..63aa4b12 100644 --- a/.copier-answers.yml +++ b/.copier-answers.yml @@ -1,24 +1,23 @@ # Do NOT update manually; changes here will be overwritten by Copier -_commit: v1.14.1 +_commit: v1.24 _src_path: https://github.com/OCA/oca-addons-repo-template.git ci: GitHub -dependency_installation_mode: PIP +convert_readme_fragments_to_markdown: false generate_requirements_txt: true github_check_license: true github_ci_extra_env: {} -github_enable_codecov: true +github_enable_codecov: false github_enable_makepot: false -github_enable_stale_action: true -github_enforce_dev_status_compatibility: true +github_enable_stale_action: false +github_enforce_dev_status_compatibility: false include_wkhtmltopdf: false +odoo_test_flavor: OCB odoo_version: 10.0 -org_name: Quartile Limited +org_name: '' org_slug: qrtl rebel_module_groups: [] repo_description: PCI Custom -repo_name: null +repo_name: '' repo_slug: pci-custom repo_website: https://www.quartile.co -travis_apt_packages: [] -travis_apt_sources: [] diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 497268be..cd433f96 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -13,7 +13,7 @@ jobs: pre-commit: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions/setup-python@v2 with: python-version: "2.7" @@ -27,6 +27,15 @@ jobs: run: pip install pre-commit - name: Run pre-commit run: pre-commit run --all-files --show-diff-on-failure --color=always + env: + # Consider valid a PR that changes README fragments but doesn't + # change the README.rst file itself. It's not really a problem + # because the bot will update it anyway after merge. This way, we + # lower the barrier for functional contributors that want to fix the + # readme fragments, while still letting developers get README + # auto-generated (which also helps functionals when using runboat). + # DOCS https://pre-commit.com/#temporarily-disabling-hooks + SKIP: oca-gen-addon-readme - name: Check that all files generated by pre-commit are in git run: | newfiles="$(git ls-files --others --exclude-from=.gitignore)" diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml deleted file mode 100644 index 1693a125..00000000 --- a/.github/workflows/stale.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: Mark stale issues and pull requests - -on: - schedule: - - cron: "0 12 * * 0" - -jobs: - stale: - runs-on: ubuntu-latest - steps: - - name: Stale PRs and issues policy - uses: actions/stale@v4 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - # General settings. - ascending: true - remove-stale-when-updated: true - # Pull Requests settings. - # 120+30 day stale policy for PRs - # * Except PRs marked as "no stale" - days-before-pr-stale: 120 - days-before-pr-close: 30 - exempt-pr-labels: "no stale" - stale-pr-label: "stale" - stale-pr-message: > - There hasn't been any activity on this pull request in the past 4 months, so - it has been marked as stale and it will be closed automatically if no - further activity occurs in the next 30 days. - - If you want this PR to never become stale, please ask a PSC member to apply - the "no stale" label. - # Issues settings. - # 180+30 day stale policy for open issues - # * Except Issues marked as "no stale" - days-before-issue-stale: 180 - days-before-issue-close: 30 - exempt-issue-labels: "no stale,needs more information" - stale-issue-label: "stale" - stale-issue-message: > - There hasn't been any activity on this issue in the past 6 months, so it has - been marked as stale and it will be closed automatically if no further - activity occurs in the next 30 days. - - If you want this issue to never become stale, please ask a PSC member to - apply the "no stale" label. - - # 15+30 day stale policy for issues pending more information - # * Issues that are pending more information - # * Except Issues marked as "no stale" - - name: Needs more information stale issues policy - uses: actions/stale@v4 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - ascending: true - only-labels: "needs more information" - exempt-issue-labels: "no stale" - days-before-stale: 15 - days-before-close: 30 - days-before-pr-stale: -1 - days-before-pr-close: -1 - remove-stale-when-updated: true - stale-issue-label: "stale" - stale-issue-message: > - This issue needs more information and there hasn't been any activity - recently, so it has been marked as stale and it will be closed automatically - if no further activity occurs in the next 30 days. - - If you think this is a mistake, please ask a PSC member to remove the "needs - more information" label. diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7031ce8f..a552be31 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,6 +37,7 @@ jobs: include: - container: ghcr.io/oca/oca-ci/py2.7-ocb10.0:latest name: test with OCB + makepot: "false" services: postgres: image: postgres:9.6 @@ -47,7 +48,7 @@ jobs: ports: - 5432:5432 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: persist-credentials: false - name: Install addons and dependencies @@ -56,11 +57,11 @@ jobs: run: manifestoo -d . check-licenses - name: Check development status run: manifestoo -d . check-dev-status --default-dev-status=Beta + continue-on-error: true - name: Initialize test db run: oca_init_test_database - name: Run tests run: oca_run_tests - - uses: codecov/codecov-action@v1 - name: Update .pot files run: oca_export_and_push_pot https://x-access-token:${{ secrets.GIT_PUSH_TOKEN }}@github.com/${{ github.repository }} if: ${{ matrix.makepot == 'true' && github.event_name == 'push' && github.repository_owner == 'qrtl' }} diff --git a/.gitignore b/.gitignore index 9c283fd4..2b045db3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ __pycache__/ *.py[cod] /.venv /.pytest_cache +/.ruff_cache # C extensions *.so @@ -24,6 +25,15 @@ var/ *.egg *.eggs +# Debian packages +*.deb + +# Redhat packages +*.rpm + +# MacOS packages +*.dmg + # Installer logs pip-log.txt pip-delete-this-directory.txt diff --git a/.oca_hooks.cfg b/.oca_hooks.cfg new file mode 100644 index 00000000..1f3e3e42 --- /dev/null +++ b/.oca_hooks.cfg @@ -0,0 +1,2 @@ +[MESSAGES_CONTROL] +disable=xml-deprecated-data-node,xml-deprecated-tree-attribute diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3c5d3eef..6e65030a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ exclude: | # You don't usually want a bot to modify your legal texts (LICENSE.*|COPYING.*) default_language_version: - python: python3 + python: python3.6 repos: - repo: https://github.com/oca/maintainer-tools rev: ab1d7f6 diff --git a/.pylintrc b/.pylintrc index fedf0ae6..f930a16a 100644 --- a/.pylintrc +++ b/.pylintrc @@ -4,7 +4,7 @@ score=n [ODOOLINT] readme_template_url="https://github.com/OCA/maintainer-tools/blob/master/template/module/README.rst" -manifest_required_authors=Quartile Limited +; manifest_required_authors= manifest_required_keys=license manifest_deprecated_keys=description,active license_allowed=AGPL-3,GPL-2,GPL-2 or any later version,GPL-3,GPL-3 or any later version,LGPL-3 @@ -103,7 +103,7 @@ enable=anomalous-backslash-in-string, eval-referenced, license-allowed, manifest-author-string, - manifest-required-author, + ; manifest-required-author, manifest-required-key, manifest-version-format, api-one-deprecated, diff --git a/.pylintrc-mandatory b/.pylintrc-mandatory index de3fd6ca..4df6dbf0 100644 --- a/.pylintrc-mandatory +++ b/.pylintrc-mandatory @@ -4,7 +4,7 @@ score=n [ODOOLINT] readme_template_url="https://github.com/OCA/maintainer-tools/blob/master/template/module/README.rst" -manifest_required_authors=Quartile Limited + manifest_required_keys=license manifest_deprecated_keys=description,active license_allowed=AGPL-3,GPL-2,GPL-2 or any later version,GPL-3,GPL-3 or any later version,LGPL-3 @@ -57,7 +57,7 @@ enable=anomalous-backslash-in-string, eval-referenced, license-allowed, manifest-author-string, - manifest-required-author, + manifest-required-key, manifest-version-format diff --git a/README.md b/README.md index aaa46417..aff8979b 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ This part will be replaced when running the oca-gen-addons-table script from OCA This repository is licensed under [AGPL-3.0](LICENSE). -However, each module can have a totally different license, as long as they adhere to Quartile Limited +However, each module can have a totally different license, as long as they adhere to policy. Consult each module's `__manifest__.py` file, which contains a `license` key that explains its license. diff --git a/purchase_received_qty_adj/__manifest__.py b/purchase_received_qty_adj/__manifest__.py index 6b16d25c..9ff9ced7 100644 --- a/purchase_received_qty_adj/__manifest__.py +++ b/purchase_received_qty_adj/__manifest__.py @@ -15,8 +15,8 @@ - Add transfer reference to stock move tree view """, 'depends': [ - 'purchase', 'purchase_stock_picking_return_invoicing', + 'purchase_open_qty', ], 'data': [ 'views/purchase_order_view.xml', diff --git a/purchase_received_qty_adj/views/purchase_order_view.xml b/purchase_received_qty_adj/views/purchase_order_view.xml index 310d4f39..24a58f7b 100644 --- a/purchase_received_qty_adj/views/purchase_order_view.xml +++ b/purchase_received_qty_adj/views/purchase_order_view.xml @@ -3,10 +3,10 @@ purchase.order.form purchase.order - + + position="attributes"> True + + + + Shipment Schedule Report + shipment.schedule.report + ir.actions.report.xml + stock_shipment_schedule_report.shipment_schedule_report + xlsx + + + + diff --git a/stock_shipment_schedule_report/reports/__init__.py b/stock_shipment_schedule_report/reports/__init__.py new file mode 100644 index 00000000..b84391c8 --- /dev/null +++ b/stock_shipment_schedule_report/reports/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- + +from . import shipment_schedule_report +from . import shipment_schedule_report_line +from . import shipment_schedule_report_compute +from . import shipment_schedule_xlsx diff --git a/stock_shipment_schedule_report/reports/shipment_schedule_report.py b/stock_shipment_schedule_report/reports/shipment_schedule_report.py new file mode 100644 index 00000000..ad46b8e5 --- /dev/null +++ b/stock_shipment_schedule_report/reports/shipment_schedule_report.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Quartile Limited +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo import models, fields + + +class ShipmentScheduleReport(models.TransientModel): + # class fields are defined here + _name = 'shipment.schedule.report' + + threshold_date = fields.Date() + limit_locs = fields.Boolean() + website_published = fields.Boolean() + categ_name = fields.Char() + p2 = fields.Char() + p3 = fields.Char() + p4 = fields.Char() + p5 = fields.Char() + p6 = fields.Char() + + # # Data fields, used to browse report data + categ_id = fields.Many2one( + comodel_name='product.category', + ) + line_ids = fields.One2many( + comodel_name='shipment.schedule.report.line', + inverse_name='report_id' + ) diff --git a/stock_shipment_schedule_report/reports/shipment_schedule_report_compute.py b/stock_shipment_schedule_report/reports/shipment_schedule_report_compute.py new file mode 100644 index 00000000..4399d2bf --- /dev/null +++ b/stock_shipment_schedule_report/reports/shipment_schedule_report_compute.py @@ -0,0 +1,260 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Quartile Limited +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from datetime import datetime +from dateutil.relativedelta import relativedelta +import pytz +from collections import Counter + +from odoo import models, fields, api, _ +from odoo.exceptions import Warning + + +class ShipmentScheduleReportCompute(models.TransientModel): + # only methods are defined here + _inherit = 'shipment.schedule.report' + + @api.multi + def print_report(self): + self.ensure_one() + threshold_date = self.threshold_date or False + category_id = self.categ_id or False + dates = self._get_dates(threshold_date) + periods = self._get_periods(dates) + self.write(self._get_header_periods(periods)) + lines = self._get_lines(periods, category_id) + for line in lines: + self.env['shipment.schedule.report.line'].create(line) + self.refresh() + report_name = 'stock_shipment_schedule_report.shipment_schedule_report' + return self.env['report'].get_action(self, report_name) + + def _get_dates(self, threshold_date): + tz = pytz.timezone(self.env.user.tz) or pytz.utc + thres_date = datetime.strptime(threshold_date, '%Y-%m-%d') + thres_date_local = tz.localize(thres_date, is_dst=None) + thres_date_utc = thres_date_local.astimezone(pytz.utc) + return { + 'current_date_local': fields.Datetime.context_timestamp( + self, datetime.now()), + 'current_date_utc': datetime.now(), + 'threshold_date_utc': thres_date_utc + } + + def _get_periods(self, dates): + periods = {} + current_date = dates['current_date_utc'] + threshold_date = dates['threshold_date_utc'] + i = 0 + for _n in xrange(7): + if i == 0: # for "Shipment on Hold" for the 1st period (period 2) + start = current_date - relativedelta(years=100) + end = current_date + elif i == 1: # for "Shipment" for the 1st period (period 2) + start = current_date + end = threshold_date + relativedelta(days=1) + elif i == 2: + start = threshold_date - relativedelta(years=100) + end = threshold_date + relativedelta(days=1) + else: + start = end + if not i == 6: + end = start + relativedelta(days=7) + else: # for the last period + end += relativedelta(years=100) + periods[i] = { + 'start': start, + 'end': end, + } + i += 1 + return periods + + def _get_header_periods(self, periods): + title_vals = {} + tz = pytz.timezone(self.env.user.tz) or pytz.utc + for p in periods: + if p >= 2: # period 0 and 1 are ignored + if p == 2: + start = '' + else: + start = datetime.strftime( + periods[p]['start'].astimezone(tz), '%Y-%m-%d') + if p == 6: + end = '' + else: + end = datetime.strftime( + periods[p]['end'].astimezone(tz) - relativedelta( + days=1), '%Y-%m-%d') + title_vals['p' + repr(p)] = start + ' ~ ' + end + return title_vals + + def _get_product_ids(self, category_id): + domain = [ + ('company_id', '=', self.env.user.company_id.id), + ('sale_ok', '=', True), + ('type', '=', 'product'), + ('active', '=', True) + ] + # identify all categories under the selected category including itself + if category_id: + categs = [category_id] + for categ in categs: + if categ.child_id: + for child_categ in categ.child_id: + ( + categs.append(child_categ) + if child_categ not in categs + else categs + ) + categ_ids = [categ.id for categ in categs] + domain.append(('categ_id', 'in', categ_ids)) + if self.website_published: + domain.append(('website_published', '=', True)) + prod_ids = self.env['product.product'].search(domain) + if not prod_ids: + raise Warning(_("There is no product to meet the condition (i.e. " + "'Saleable', 'Stockable' and belonging to the " + "selected Product Category or its offsprings).")) + return prod_ids + + def _get_move_qty_data(self, params): + res = {} + sql = """ + SELECT + m.product_id, + SUM(m.product_qty / u.factor) + FROM + stock_move m + LEFT JOIN + product_uom u ON m.product_uom = u.id + WHERE + location_id %s (%s) AND + location_dest_id %s (%s) AND + product_id IN (%s) AND + state NOT IN ('done', 'cancel') AND + m.date_expected >= '%s' AND + m.date_expected < '%s' + GROUP BY + product_id + """ % (tuple(params)) + self.env.cr.execute(sql) + qty_dict = self.env.cr.dictfetchall() + for rec in qty_dict: + res[rec['product_id']] = rec['sum'] + return res + + def _get_quote_qty_data(self, params): + res = {} + sql = """ + SELECT + ol.product_id, + SUM(ol.product_uom_qty / u.factor) + FROM + sale_order_line ol + LEFT JOIN + product_uom u ON ol.product_uom = u.id + WHERE + product_id IN (%s) AND + order_id in ( + SELECT + id + FROM + sale_order + WHERE + state = 'draft' AND + expected_date >= '%s' AND + expected_date < '%s' + ) + GROUP BY product_id + """ % (tuple(params)) + self.env.cr.execute(sql) + qty_dict = self.env.cr.dictfetchall() + for rec in qty_dict: + res[rec['product_id']] = rec['sum'] + return res + + def _get_locs(self): + loc_domain = [ + ('company_id', '=', self.env.user.company_id.id), + ('usage', '=', 'internal'), + ('active', '=', True) + ] + if self.limit_locs: + loc_domain.append(('consider_qty', '=', True)) + return self.env['stock.location'].search(loc_domain) + + def _get_qty_data(self, products, periods, line_vals): + res = [] + # convert tuples into strings to avoid sql error due to trailing comma + # (,) in case there is only one id returned + prod_ids = tuple([p.id for p in products]) + prod_ids_str = ', '.join(map(repr, prod_ids)) + loc_ids = tuple([l.id for l in self._get_locs()]) + loc_ids_str = ', '.join(map(repr, loc_ids)) + i = 0 + for _i in xrange(7): + date_from = fields.Datetime.to_string(periods[i]['start']) + date_to = fields.Datetime.to_string(periods[i]['end']) + in_params = ['NOT IN', loc_ids_str, 'IN', loc_ids_str, + prod_ids_str, date_from, date_to] + out_params = ['IN', loc_ids_str, 'NOT IN', loc_ids_str, + prod_ids_str, date_from, date_to] + quote_params = [prod_ids_str, date_from, date_to] + for type in ['in', 'out']: + if type == 'in': + qty_data = self._get_move_qty_data(in_params) + else: + move_qty_data = self._get_move_qty_data(out_params) + quote_qty_data = self._get_quote_qty_data(quote_params) + qty_data = dict( + Counter(move_qty_data) + Counter(quote_qty_data)) + for k, v in qty_data.iteritems(): + line_vals[k][type + repr(i)] = v + i += 1 + for prod in line_vals: + i = 2 # start from period "2" (period until threshold date) + for _i in xrange(5): + line_vals[prod]['bal' + repr(i)] \ + = line_vals[prod]['bal' + repr(i - 1)] + line_vals[prod][ + 'in' + repr(i)] - line_vals[prod]['out' + repr(i)] + i += 1 + res.append(line_vals) + return res + + def _get_qoh(self, prod): + locs = self._get_locs() + quants = self.env['stock.quant'].search( + [('company_id', '=', self.env.user.company_id.id), + ('product_id', '=', prod.id), + ('location_id', 'in', [l.id for l in locs])] + ) + return sum(q.qty for q in quants) if quants else 0.0 + + def _get_lines(self, periods, category_id): + res = [] + line_vals = {} + products = self._get_product_ids(category_id) + for prod in products: + qoh = self._get_qoh(prod) + line_vals[prod.id] = { + 'report_id': self.id, + 'product_id': prod.id, + 'product_name': prod.display_name, + 'categ_id': prod.categ_id.id, + 'categ_name': prod.categ_id.display_name, + 'bal1': qoh, + 'in0': 0.0, 'out0': 0.0, + 'in1': 0.0, 'out1': 0.0, + 'in2': 0.0, 'out2': 0.0, 'bal2': 0.0, # first period in output + 'in3': 0.0, 'out3': 0.0, 'bal3': 0.0, # second period in output + 'in4': 0.0, 'out4': 0.0, 'bal4': 0.0, # third period in output + 'in5': 0.0, 'out5': 0.0, 'bal5': 0.0, # fourth period in output + 'in6': 0.0, 'out6': 0.0, 'bal6': 0.0, # fifth period in output + } + lines = self._get_qty_data(products, periods, line_vals) + for line in lines: # only append values (without key) to form the list + for k, v in line.iteritems(): + res.append(v) + res = sorted(res, key=lambda k: (k['categ_name'], k['product_name'])) + return res diff --git a/stock_shipment_schedule_report/reports/shipment_schedule_report_line.py b/stock_shipment_schedule_report/reports/shipment_schedule_report_line.py new file mode 100644 index 00000000..8ef7e3a1 --- /dev/null +++ b/stock_shipment_schedule_report/reports/shipment_schedule_report_line.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Quartile Limited +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo import models, fields + + +class ScheduleReportLine(models.TransientModel): + _name = 'shipment.schedule.report.line' + + report_id = fields.Many2one( + comodel_name='shipment.schedule.report', + ondelete='cascade', + index=True, + ) + product_id = fields.Many2one( + comodel_name='product.product', + index=True, + ) + categ_id = fields.Many2one( + comodel_name='product.category', + ) + + product_name = fields.Char() + categ_name = fields.Char() + bal1 = fields.Float() + in0 = fields.Float() + out0 = fields.Float() + in1 = fields.Float() + out1 = fields.Float() + in2 = fields.Float() # first period in output + out2 = fields.Float() + bal2 = fields.Float() + in3 = fields.Float() # second period in output + out3 = fields.Float() + bal3 = fields.Float() + in4 = fields.Float() # third period in output + out4 = fields.Float() + bal4 = fields.Float() + in5 = fields.Float() # fourth period in output + out5 = fields.Float() + bal5 = fields.Float() + in6 = fields.Float() # fifth period in output + out6 = fields.Float() + bal6 = fields.Float() diff --git a/stock_shipment_schedule_report/reports/shipment_schedule_xlsx.py b/stock_shipment_schedule_report/reports/shipment_schedule_xlsx.py new file mode 100644 index 00000000..4202ba50 --- /dev/null +++ b/stock_shipment_schedule_report/reports/shipment_schedule_xlsx.py @@ -0,0 +1,195 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Quartile Limited +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from datetime import datetime + +from odoo import fields, _ +from odoo.addons.abstract_report_xlsx.reports \ + import stock_abstract_report_xlsx +from odoo.report import report_sxw + + +class ShipmentScheduleXlsx(stock_abstract_report_xlsx.StockAbstractReportXlsx): + + def __init__(self, name, table, rml=False, parser=False, header=True, + store=False): + super(ShipmentScheduleXlsx, self).__init__( + name, table, rml, parser, header, store) + + def _get_report_name(self): + return _('Shipment Schedule Report') + + def _get_report_columns(self, report): + return { + 0: { + 'header': _('Product'), + 'field': 'product_name', + 'width': 30 + }, + 1: { + 'header': _('Product Category'), + 'field': 'categ_name', + 'width': 32 + }, + 2: { + 'header': _('Inv. on Hand'), + 'field': 'bal1', + 'type': 'number', + 'width': 8 + }, + 3: { + 'header': _('Receipt'), + 'field': 'in2', + 'type': 'number', + 'width': 8 + }, + 4: { + 'header': _('Shipt. on Hold'), + 'field': 'out0', + 'type': 'number', + 'width': 8 + }, + 5: { + 'header': _('Shipt.'), + 'field': 'out1', + 'type': 'number', + 'width': 8 + }, + 6: { + 'header': _('Balance'), + 'field': 'bal2', + 'type': 'number', + 'width': 8 + }, + 7: { + 'header': _('Receipt'), + 'field': 'in3', + 'type': 'number', + 'width': 8 + }, + 8: { + 'header': _('Shipt.'), + 'field': 'out3', + 'type': 'number', + 'width': 8 + }, + 9: { + 'header': _('Balance'), + 'field': 'bal3', + 'type': 'number', + 'width': 8 + }, + 10: { + 'header': _('Receipt'), + 'field': 'in4', + 'type': 'number', + 'width': 8 + }, + 11: { + 'header': _('Shipt.'), + 'field': 'out4', + 'type': 'number', + 'width': 8 + }, + 12: { + 'header': _('Balance'), + 'field': 'bal4', + 'type': 'number', + 'width': 8 + }, + 13: { + 'header': _('Receipt'), + 'field': 'in5', + 'type': 'number', + 'width': 8 + }, + 14: { + 'header': _('Shipt.'), + 'field': 'out5', + 'type': 'number', + 'width': 8 + }, + 15: { + 'header': _('Balance'), + 'field': 'bal5', + 'type': 'number', + 'width': 8 + }, + 16: { + 'header': _('Receipt'), + 'field': 'in6', + 'type': 'number', + 'width': 8 + }, + 17: { + 'header': _('Shipt.'), + 'field': 'out6', + 'type': 'number', + 'width': 8 + }, + 18: { + 'header': _('Balance'), + 'field': 'bal6', + 'type': 'number', + 'width': 8 + }, + } + + def _get_report_filters(self, report): + report_date = fields.Datetime.to_string( + fields.Datetime.context_timestamp( + report, datetime.now() + ) + ) + return [ + [_('Report Date'), report_date], + [_('Threshold Date'), report.threshold_date], + [_('Limit Locations'), 'True' if report.limit_locs else 'False'], + [_('Product Category'), report.categ_name or 'All Categories'], + ] + + def _get_col_count_filter_name(self): + return 1 + + def _get_col_count_filter_value(self): + return 1 + + def _get_periods(self, report): + periods = { + 3: { + 'header': report.p2, + 'col_plus': 3 + }, + 7: { + 'header': report.p3, + 'col_plus': 2 + }, + 10: { + 'header': report.p4, + 'col_plus': 2 + }, + 13: { + 'header': report.p5, + 'col_plus': 2 + }, + 16: { + 'header': report.p6, + 'col_plus': 2 + } + } + return periods + + def _generate_report_content(self, workbook, report): + periods = self._get_periods(report) + self.write_header_periods(periods) + self.write_array_header() + for line in report.line_ids: + self.write_line(line) + + +ShipmentScheduleXlsx( + 'report.stock_shipment_schedule_report.shipment_schedule_report', + 'shipment.schedule.report', + parser=report_sxw.rml_parse +) diff --git a/stock_shipment_schedule_report/wizards/__init__.py b/stock_shipment_schedule_report/wizards/__init__.py new file mode 100644 index 00000000..47d88d12 --- /dev/null +++ b/stock_shipment_schedule_report/wizards/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import shipment_schedule_report_wizard diff --git a/stock_shipment_schedule_report/wizards/shipment_schedule_report_wizard.py b/stock_shipment_schedule_report/wizards/shipment_schedule_report_wizard.py new file mode 100644 index 00000000..32a41c9e --- /dev/null +++ b/stock_shipment_schedule_report/wizards/shipment_schedule_report_wizard.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# Copyright 2017 Quartile Limited +# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). + +from odoo import models, fields, api + + +class ShipmentScheduleReportWizard(models.TransientModel): + _name = "shipment.schedule.report.wizard" + _description = 'Shipment Schedule Report Wizard' + + threshold_date = fields.Date( + string='Threshold Date', + default=fields.Date.context_today, + required=True, + ) + limit_locs = fields.Boolean( + string='Limit Locations', + default=True, + help="Only consider stock in locations that are meant to be available " + "for customers. If unselected, consider all the internal " + "locations", + ) + website_published = fields.Boolean( + string='Only Show Products Published on Website', + default=True, + help="Enable option to filter out the products that are unpublished " + "on website. Otherwise, both published and unpublished product " + "will be exported to the report", + ) + categ_id = fields.Many2one( + comodel_name='product.category', + string='Product Category', + ) + + @api.multi + def action_export_xlsx(self): + self.ensure_one() + model = self.env['shipment.schedule.report'] + report = model.create(self._prepare_report_xlsx()) + return report.print_report() + + def _prepare_report_xlsx(self): + self.ensure_one() + return { + 'threshold_date': self.threshold_date, + 'limit_locs': self.limit_locs, + 'website_published': self.website_published, + 'categ_id': self.categ_id.id or False, + 'categ_name': self.categ_id.display_name or False + } diff --git a/stock_shipment_schedule_report/wizards/shipment_schedule_report_wizard_view.xml b/stock_shipment_schedule_report/wizards/shipment_schedule_report_wizard_view.xml new file mode 100644 index 00000000..829f5a33 --- /dev/null +++ b/stock_shipment_schedule_report/wizards/shipment_schedule_report_wizard_view.xml @@ -0,0 +1,44 @@ + + + + + shipment.schedule.report.wizard + shipment.schedule.report.wizard + +
+ + + + + + +
+
+
+
+
+ + + + + +
diff --git a/test-requirements.txt b/test-requirements.txt index c9d43a25..1a5e144b 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1 +1,2 @@ +git+https://github.com/qrtl/pci-oca@10.0#subdirectory=setup/purchase_open_qty git+https://github.com/qrtl/pci-oca@10.0#subdirectory=setup/purchase_stock_picking_return_invoicing